最长回文子串问题:给定一个字符串,求它最长的回文子串的长度。
回文串:如果一个字符串的正序序列与它的逆序序列相等,那么这个字符串是回文串。
暴力求回文串长度的方法:遍历每一个字符,以该字符为中间点向两边查找。时间复杂度为O(n2),很不高效。manacher算法可以把时间复杂度提升到O(n)。
算法过程分析:
Manacher算法提供了一种巧妙地办法,将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。
举个例子:
Manacher算法用一个辅助数组Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]的长度。
对于上面的例子,可以得出Len[i]数组为:
Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度。证明:首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。
有了这个性质,那么原问题就转化为求所有的Len[i]。
Len数组的计算:
首先从左往右依次计算Len[i],当计算Len[i]时,Len[j](0<=j<i)已经计算完毕。设mx为之前计算中最长回文子串的右端点的最大值,并且设取得这个最大值的位置为id,分两种情况:
- 第一种情况:i<=mx
那么找到i相对于id的对称位置,设为j,那么如果Len[j]<mx-i,如下图:
那么说明以j为中心的回文串一定在以id为中心的回文串的内部,且j和i关于位置po对称,由回文串的定义可知,一个回文串反过来还是一个回文串,所以以i为中心的回文串的长度至少和以j为中心的回文串一样,即Len[i]>=Len[j]。因为Len[j]<mx-i,所以说i+Len[j]<mx。由对称性可知j=2id-i,所以Len[i]=Len[2id-i]。
如果Len[j]>=mx-i,由对称性,说明以i为中心的回文串可能会延伸到mx之外,而大于mx的部分我们还没有进行匹配,所以要从mx+1位置开始一个一个进行匹配,直到发生失配,从而更新mx和对应的id以及Len[i]。
综上,Len[i]=min(Len[2id-i],mx-i)
- 第二种情况: i>P
如果i比P还要大,说明对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新P的位置和对应的po以及Len[i]。
核心代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=111000;
///修改后的字符串长度是原字符串长度的两倍
char s[N],s_new[N<<1];
///辅助数组Len[i]表示以字符s_new[i]为中心的
///长回文字串的最右字符到s_new[i]的长度
int p[N<<1];
///初始化辅助数组
void init()
{
int len=strlen(s);
s_new[0]='$';
s_new[1]='#';
for(int i=0; i<len; i++)
{
s_new[i*2+2]=s[i];
s_new[i*2+3]='#';
}
s_new[2*len+2]='\0';///不要忘记'\0'
}
void manacher()
{
int len=strlen(s_new);
int id=-1;///id为右边界最大的回文串的中心
int mx=0;///mx为以s_new[id]为中心的最长回文最右边界
for(int i=1; i<len; i++)
{
if(i<mx) p[i]=min(p[id*2-i],mx-i);
else p[i]=1;
while(s_new[i+p[i]]==s_new[i-p[i]])///判断该回文串是否还可以扩充
p[i]++;
///每走一步都要更新mx,因为我们希望mx尽可能的大,
///这样才有机会执行if(i<mx),从而降低算法的时间复杂度
if(mx<i+p[i])
{
mx=i+p[i];
id=i;
}
}
}
int main()
{
scanf("%s",s);
init();
manacher();
int len=strlen(s_new);
int ans=-1;
for(int i=1; i<len; i++)
ans=max(ans,p[i]-1);
cout<<ans<<endl;
return 0;
}