问题提出
问:给定一个字符串,要求其最长回文子串。
例如:
“aaaa”,其最长回文子串为4,即“aaaa”,
“abcd”,最长回文子串为1,
“abccb”,最长回文子串为4,即“bccb”。
如果直接对每一个字符遍历,分别向两边查找的话,时间复杂度为O(
n
2
n^2
n2),效率很低。
那有没有更高效的算法呢。
1975年,一个叫Manacher的人发明了一种算法,即manacher算法,时间复杂度可以达到O(
n
n
n)。
算法描述
1 、 1、 1、预处理
由于回文串分为奇回文(长度为奇数)、和偶回文(长度为偶数),本因分两种情况处理,但为了简化这一步,我们将对原字符串进行处理,使之变成奇回文。
具体做法是在每个字符左右两边都加上一个特殊字符,如:
原字符串为,abba,长度为4,
处理后,#a#b#b#a#,长度为9。
原字符串为,aba,长度为3,
处理后,#a#b#a#,长度为7。
2 、 2、 2、最长回文子串长度
我们引入一个辅助数组,int p[ ]
,p[i]
表示以原字符串s[]
中i
位置处s[i]
为中心的最长回文半径。
以字符串 abbaf
为例,预处理后字符串为 #a#b#b#a#f#
i 0 1 2 3 4 5 6 7 8 9 10 11 12
arr[i] # c # a # b # b # a # f #
p[i] 1 2 1 2 1 2 5 2 1 2 1 2 1
在上面这个例子中,最长回文子串为abba
。以s[6]
为中心,其最长回文半径为p[6]=5
,所覆盖的字符串是#a#b#b#a#
,对应的原始字符串为abba
,即最长回文子串,长度为4
,可以通过5-1
得到。
接下来我们来探究最长回文子串长度和最长回文半径之间的关系。
再看几个例子,aba
, 转换之后为#a#b#a#
,最长回文子串为aba
,长度为3
,最长回文半径为p[4]=4
。
再如,#a#e#f#e#a#
,最长回文子串为aefea
,长度为5,计算得最长回文半径为p[6]=6
。
我们发现,最长回文子串长度=最长回文半径-1
,即p[i]-1
。
事实上,的确存在这个结论。我们可以这样理解,最长回文子串即以某一点i
为中心,以p[i]
为半径所覆盖的线段,那么对于原始字符串最长回文子串长度=p[i]*2
。由于预处理后,每个字符左右都加了一个特殊字符,所以最长回文半径变为了原来的2
倍,最长回文子串长度变为了原来的*2-1
,那么最长回文子串长度=p[i]-1
。
3 、 3、 3、计算p[]数组
现在知道了要求的最长回文子串长度与最大回文半径p[i]
有关,那么接下来就讨论最大回文半径p[i] 怎么求。
我们设置两个变量id
、mx
,id
表示能延伸到最右端的回文子串的中心位置,mx
表示该子串的最右端位置。
那么有,mx=id+p[id]
。
对于i<mx
,在以id
为中心的回文子串中,存在另一以j
为中心的回文子串,与以i
为中心的回文子串关于id
对称,且有p[j]=p[i]
。由于对称的性质,所以有i+j=id*2
,j=id*2-i
。
考虑到i
的回文子串可能存在超出mx
的部分,所以此时p[i]=mx-i
,依然有p[j]=p[i]
。
至于超出mx
的部分是否满足上述条件,就需要遍历去比较字符了。
所以,在i<mx
的情况下,有p[i]=min(p[id*2-i],mx-i)
。
当满足mx<i+p[i]
时,更新mx=i+p[i]
、id=i
。
4 、 4、 4、参考代码 C++
char s[maxn];//原始字符串
char s1[maxn];//预处理后字符串
char p[maxn];//最长回文半径
int init()//预处理
{
int len=strlen(s);
s1[0]='$';//处理边界,防止越界
s1[1]='#';
int j=2;
for(int i=0;i<len;i++)
{
s1[j++]=s[i];
s1[j++]='#';
}
s1[j]='\0';//处理边界,防止越界
return j;//返回处理后的字符串长度
}
int manacher()//计算最长回文子串长度
{
int len=init();
int maxlen=-1;//最长回文子串长度
int id;//可以不初始化是因为初始循环一定会执行if(mx<i+p[i])
int mx=0;
for(int i=1;i<len;i++)
{
if(i<mx)
p[i]=min(p[2*id-i],mx-i);
else p[i]=1;
while(s1[i-p[i]] == s1[i+p[i]])//遍历比较字符,因为已经处理过边界,所以无需进行边界判断
p[i]++;
if(mx<i+p[i])//更新id和mx
{
id=i;
mx=i+p[i];
}
maxlen=max(maxlen,p[i]-1);
}
return maxlen;//返回最长回文子串长度
}
5
、
5、
5、参考习题
洛谷P3805 【模板】manacher算法
or HDU3068 最长回文