[转]最长回文字符串
最长回文串
输入一个字符串,求出其中最长的回文字串。字串的含义是:在原串中连续出现的字符串片段。回文的含义是:正着看和倒着看相同,如aabb和yyxyy,字判断时,应该忽略所有标点符号和空格,且忽略大小写,但输出时应保持原样(在回文串的首部和尾部不要出现多余字符)。输入字符串长度不超过5000,且占据单独的一行应该输出最长的回文串,如果有多个,输出起始位置最靠左的。
样例输入:Confuciuss say:Madam,I'm Adam
样例输出:Madam,I'm Adam
[分析]
如果输入全部是大写字母,问题就简单了:直接判断每个字符串即可。可其他字符的出现把问题搞的复杂了起来。首先,我们不能用scanf("%s")来输入字符串,因为它碰到空格或者TAB键就会停下来,可以用下述两种方法解决它:
第一种方法是使用fgetc(fin),它读取一个打开的文件fin,读取一个字符,然后返回一个int 值。为什么返回的是int 而不是char呢,因为如果文件结束,fgetc将返回一个特殊标记EOF,它并不是一个char。如果把fgetc(fin)的返回值强制转换成char,将无法把特殊的EOF与普通char分开(一般情况下应该在检查它不是EOF后将其转换)。如果要从标准输入读取一个字符,可以用getchar(),它等价于fgetc(stdin)。
fgetc()和getchar()将读取“下一个字符”,一次你需要知道在各种情况下,“下一个字符是哪个”。如果用scanf("%d",&n)读取整数n,则要是在输入123后多加了一个空格,用getchar()将是读取了这个空格;如果“123”后紧跟着换行,则读取到的将是回车符"\n"。这里有个潜在的陷阱:不同操作系统的会出换行符是不一样的。Windows是'\n,'\r'两个字符,Linux的则是'\n'而MacOS是"\r",如果在Windows下读取Windows文件,fgetc()和getchar()会把'\r'吃掉,只剩下'\n';但如果要在Linux下读取同样的文件,他们会忠实的读取'\r'然后才是'\n'。如果编程不注意,你的程序可能在某个系统上是完美的,但在另一个系统上 则是一塌糊涂。当然比赛的组织方应避免此类错误,但选手也应该把自己的程序写的更加棒,容错性更好。
第2种方法使用fgets(buf,maxn,fin)读取完整的一行,其中buf的生命为char buf[maxn]。这个函数读取不超过maxn-1个字符,然后再末尾添上'\0',因此不会出现越界的情况。之所以说可以用这个函数读取完整的一行,是因为一旦读到'\n',读取工作就会停止,而这个'\n'也会是字符串中最后一个有效字符(再往后就是结束标志'\0'了)。只有一种情况字符串不会以'\n'结尾:读到文件结束符,并且文件最后不是以'\n'结尾。尽管组织方会尽量避免这种情况,但我们应该把程序写的更加棒。
和getc()一样,fgets也有一个“表针输入板”gets。遗憾的是,gets和他的兄弟fgets差别比较大:它的用法师gets(s),没有指明读取的最大字符数。这里就出现了一个潜在的问题:gets将不停的往s里塞东西,而不管是否能装下!gets不管s的可用空间有多少。
说了这么多,我们终于解决了“输入有空格的问题”。我们选用fgets(buf,maxn,fin)函数,它可以一次性读取一行,最为方便。
接下来,需要解决判断时忽略标点输出时按原样的问题了。首先输入的标点符号不能直接删除,不然输出时就无法按原样了,但如果每次判断时都跳过标点符号又太麻烦。
这里介绍一个通用的方案:预处理。即构造一个新的字符串,不包含原来的标点符号,而且把所有字符全部变成打写(顺便解决了大小写的问题):
n=strlen(buf);
m=0;
for(int i=0;i<n;i++)
if(isalpha(buf[i]))
s[m++]=toupper(buf[i]);//toupper()将字符全部转换成大写,需要头文件ctype.h
上述代码用到了一个新函数:ctype.h中的isalpha(c),它用于判断字符c是否为大写或小写字母。用toupper(c)返回c的大写形式。经过预处理后,s保存的就是buf中字母的大写形式了,此外,c-'a'+'A'也可将小写字母变大写字母。注意:头文件ctype.h中的isalpha isdigit isprint可以用来判断字符的特性,而toupper,tolower可以用来转换大小写。
接下来问题就简单了:枚举字符串的起点和终点,然后判断是否是回文串。
int max=0;
for(i=0;i<m;i++)
{
for(j=i;j<m;j++)
if(s[i....j]是回文串&&j-i+1>max)//“最大值”变量max,它保存的是目前为止发现的最长的回文子串的长度,
max=j-i+1; //如果s[i......j]是回文子串,j-i+1是否超过max,
} //此外循环变量不能为i或者j,以为题目已经在外层用过了,s[k]的对称位置是s[j-(k-i)]=s[i+j-k]
下面是目前为止的程序,
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4
5 const int maxn=5000+10;//用一个常变量代替常数,便于修改,+10防止意外情况发生
6 char buf[maxn],s[maxn];//s数组用于预处理
7 int main()
8 {
9 int n,m=0,max=0;
10 int i,j,k;
11 fgets(buf,sizeof(s),stdin);
12 n=strlen(buf);
13 for(i=0;i<n;i++)
14 {
15 if(isalpha(buf[i]))
16 s[m++]=toupper(buf[i]);
17 }
18 for(i=0;i<m;i++)
19 for(j=i;j<m;j++)
20 {
21 int ok=1;
22 for(k=i;k<=j;k++)
23 if(s[k]!=s[i+j-k])
24 ok=0;
25 if(ok&&j-i+1>max)
26 max=j-i+1;
27 }
28 printf("max=%d\n",max);
29 return 0;
30 }
这个程序还有一些功能没有完成,但离成功以不远,在编写较复杂的程序时,除用伪代码理清思路外还可采用迭代式开发,先写出骨架,每次只实现一部分功能,但要保证其正常。经测试,上面的程序可求出最长字串的长度为11,就下来就是原样输出且尽量靠左,靠左条件已满足,我们就是自左至右枚举的,且只在i-j+1严格大于max时,才更新max,接下来就是输出。
经过一番思考,似乎小的改动是不做够的,因为我们无从得知s[i],s[j]在原串中的位置,因此我们必须增加一个数组p,用p[i]来保存s[i]在原串buf中的位置,然后在更新max的同时,将p[i],p[j]保存到x,y。最后输出buf[i],buf[j]间的所有字符即可。
现在看上去已经很完美了,但你不觉得程序运行有点慢吗,字符串长度有可能到达5000啊,其实我们可以枚举字符串中间位置然后向外扩展,知道有不同字符出现。下面是完整程序。注意:长度为奇数和偶数的处理方法不同
1 //最长回文字串
2 //输入一个字符串,求输出的最长回文字串。应判断所有标点符号及空格
3 //但输出保持原样
4 //出入长度补超过5000,战单独一行,输出最长回文字串,如果有多个,
5 //输出最靠左的
6 //输入:Confuciuss say:Madam,I'm Adam 输出:Madam,I'm Adam
7
8 #include <stdio.h>
9 #include <iostream>
10 #include <string.h>
11 #include <ctype.h>
12 using namespace std;
13 #define maxn 5000+10
14 char buf[maxn], s[maxn];//用于输入字符串,预处理字符串
15 int p[maxn];//存储字符串相应的位置
16 int main()
17 {
18 int n,m=0,max=0;
19 int x,y;
20 int i,j;
21 fgets(buf,sizeof(buf),stdin);//fgets()比gets()函数要更好
22 n=strlen(buf);
23 for(i=0;i<n;i++)
24 {
25 if(isalpha(buf[i]))//判断buf[i]是否为字母
26 {
27 p[m]=i;
28 s[m++]=toupper(buf[i]);//转换为大写
29 }
30 }
31 for(i=0;i<m;i++)//检举回文串的中间位置i,并向外扩展
32 {
33 for(j=0;i-j>=0&&i+j<m;j++)//长度为基数
34 {
35 if(s[i-j]!=s[i+j]) break;
36 if(j*2+1>max)
37 {
38 max=j*2+1;
39 x=p[i-j];
40 y=p[i+j];
41 }
42 }
43 for(j=0;i-j>=0&&i+j+1<m;j++)//长度为偶数
44 {
45 if(s[i-j]!=s[i+j+1]) break;
46 if(j*2+2>max)
47 {
48 max=j*2+2;
49 x=p[i-j];
50 y=p[i+j+1];
51 }
52 }
53 }
54 for(i=x;i<=y;i++)
55 printf("%c",buf[i]);
56 printf("\n");
57
58 return 0;
59 }