manacher算法【最长回文子串】

版权声明:本文为博主原创文章,转载请注明出处( • ̀ω•́ )✧ https://blog.csdn.net/wangws_sb/article/details/89107982

最长回文子串问题:给定一个字符串,求它最长的回文子串的长度。

回文串:如果一个字符串的正序序列与它的逆序序列相等,那么这个字符串是回文串。

暴力求回文串长度的方法:遍历每一个字符,以该字符为中间点向两边查找。时间复杂度为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;
}

猜你喜欢

转载自blog.csdn.net/wangws_sb/article/details/89107982