[转]最长回文字符串

      最长回文串

        输入一个字符串,求出其中最长的回文字串。字串的含义是:在原串中连续出现的字符串片段。回文的含义是:正着看和倒着看相同,如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 }

 

posted on 2012-12-10 20:36  行者1992  阅读(1483)  评论(0编辑  收藏  举报