Problem Description:
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等
下面介绍一种复杂度只有O(n)的Manacher算法:
首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号。比如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#。 为了进一步减少编码的复杂度,可以在字符串的开始加入另一个特殊字符,这样就不用特殊处理越界问题,比如$#a#b#a#。
下面以字符串12212321为例,经过上一步,变成了 S[] = "$#1#2#2#1#2#3#2#1#";
然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i]),比如S和P的对应关系:
可以看出,P[i]-1正好是原字符串中回文串的总长度,可证明。
那么怎么计算P[i]呢?该算法增加两个辅助变量id和mx,其中id表示最大回文子串中心的位置,mx则为id+P[id],表示在i之前的回文串中延伸至最右端的位置也就是最大回文子串的边界。for(int i = 1; i < len; ++i) { if(mx > i) P[i] = MIN(P[id*2-i] , mx-i); else P[i] = 1; while(S[i+P[i]] == S[i-P[i]]) ++P[i]; if(i + P[i] > mx) { mx = i + P[i]; id = i; } }
代码:
int longestPalindromic(string str) { int strsz = str.size(); if(strsz<=1)return strsz; int len = strsz*2+2; int* s = new int[len+1]; s[0]='$'; for(int i=1,j=0; j<strsz; j++) { s[i++] = '#'; s[i++] = str[j]; } s[len-1]='#'; s[len]='\0'; int id = 0; int mx = 0; int longestnum = 1; int longestid = 0; int* p = new int[len]; for(int i=1; i<len; i++) { if(mx>i) p[i]=min(p[id*2-i],mx-i); else p[i]=1; while(s[i+p[i]] == s[i-p[i]]) p[i]++; if(p[i]+i>mx) { mx=p[i]+i; id=i; } if(p[i]>longestnum) { longestnum=p[i]; longestid = i; } } delete[] p; delete[] s; cout<<"Longest Palindromic Num:"<<longestnum-1<<endl; cout<<str.substr((longestid-1)>>1-(longestnum-1)>>1,longestnum-1)<<endl; return longestnum-1; }
其实这题也可以用后缀数组来做,但是后缀数组的复杂度太高:翻转拼接(O(n)),排序(O(nlogn)),找最长的串(O(2n*len)),找的时候还要注意一个相邻的两个串一个有#一个没有#。
http://blog.csdn.net/ggggiqnypgjg/article/details/6645824
http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/