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;
}
}
算法的关键点在这里:如果mx>i,那么P[i] >= MIN(P[2*id-i], mx-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.youkuaiyun.com/ggggiqnypgjg/article/details/6645824
http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/