manacher算法详解

参考博客:https://blog.csdn.net/qq_43152052/article/details/100784978

马拉车算法步骤:

1、由于回文串存在奇回文串和偶回文串,马拉车算法第一步就是:预处理字符串,做法是在每一个字符的左右都加上一个特殊字符(前提是这个字符在字符串没有出现过),使这两种回文串都变成奇回文串。比如加上’#’,这样奇回文串(bab)还是会变成奇回文串(#b#a#b#),偶回文串(noon)会变成奇回文串(#n#o#o#n#)。

--------------------------------------------------------------------------------------------------------------------------
2、然后我们定义一个辅助数组p用来表示经过与处理过的新字符串t,其中p[i]表示以字符t[i]为半径的回文子串长度,例如:

index    0    1    2    3    4    5    6    7    8    9    10    11    12    13
char     $    #    1    #    2    #    2    #    1    #    2     #     2     #
R        1    2    1    2    5    2    1    6    1    2    3     2     1

--------------------------------------------------------------------------------------------------------------------------

3、找规律 

规律①:最大半径减1等于最长回文串的长度
看上面那个例子,以中间的 ‘1’ 为中心的回文子串 “#2#2#1#2#2#” 的半径是6,而未添加#号的回文子串为 “22122”,长度是5,为半径减1。这是个普遍的规律么?我们再看看之前的那个 “#b#o#b#”,我们很容易看出来以中间的 ‘o’ 为中心的回文串的半径是4,而 "bob"的长度是3,符合规律。再来看偶数个的情况 “noon”,添加#号后的回文串为 “#n#o#o#n#”,以最中间的 ‘#’ 为中心的回文串的半径是5,而 “noon” 的长度是4,完美符合规律。所以我们只要找到了最大的半径,就知道最长的回文子串的字符个数了。只知道长度无法定位子串,我们还需要知道子串的起始位置。

规律②:最长回文字符的起始位置是中间位置减去半径在除以2
我们还是先来看中间的 ‘1’ 在字符串 “#1#2#2#1#2#2#” 中的位置是7,而半径是6,貌似 7-6=1,刚好就是回文子串 “22122” 在原串 “122122” 中的起始位置1。那么我们再来验证下 “bob”,“o” 在 “#b#o#b#” 中的位置是3,但是半径是4,这一减成负的了,肯定不对。所以我们应该至少把中心位置向后移动一位,才能为0啊,那么我们就需要在前面增加一个字符,这个字符不能是#号,也不能是s中可能出现的字符,所以我们暂且就用美元号吧,毕竟是博主最爱的东西嘛。这样都不相同的话就不会改变p值了,那么末尾要不要对应的也添加呢,其实不用的,不用加的原因是字符串的结尾标识为 ‘\0’,等于默认加过了。那此时 “o” 在 "$#b#o#b#"中的位置是4,半径是4,一减就是0了,貌似没啥问题。我们再来验证一下那个数字串,中间的 ‘1’ 在字符串 "$#1#2#2#1#2#2#" 中的位置是8,而半径是6,这一减就是2了,而我们需要的是1,所以我们要除以2。之前的 “bob” 因为相减已经是0了,除以2还是0,没有问题。再来验证一下 “noon”,中间的 ‘#’ 在字符串 "$#n#o#o#n#" 中的位置是5,半径也是5,相减并除以2还是0,完美。所以,最长回文字符的起始位置是中间位置减去半径在除以2。

--------------------------------------------------------------------------------------------------------------------------

4、p数组求解


关于p数组的求解,需要建立两个辅助变量mx和id,id表示回文串的中心位置下标,mx表示回文串右边最大半径下标,所以mx = id + p[id]。

接下来就是求p[i],当然这也是算法中最重要的部分:

p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;

注: 2 * id - i表示 i 关于 id 对称的坐标点j。因为 j 到 id 之间到距离等于 id 到 i 之间到距离(id - j = i - id),所以j = 2 * id - i。 

如果 mx > i, 则 p[i] = min( p[2 * id - i] , mx - i );否则,p[i] = 1。

①: 在mx > i的前提下,若p[j] < mx - i,表示以 S[j] 为中心的回文子串包含在以 S[id] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的回文子串必然包含在以 S[id] 为中心的回文子串中,所以必有 P[i] = P[j]。

②: 在mx > i的前提下,若 p[j] >= mx - i表示以 S[j] 为中心的回文子串不一定完全包含于以 S[id] 为中心的回文子串中,也就是说p[j]表示的回文串半径超过mx对称点的坐标了,那么此时不能利用对称性了,但我们一定可以扩展到 mx 的,至于mx之后的部分我们任然需要匹配了。

③: 在mx <= i的前提下,p[i] = 1,因为此时我们需要通过中心扩展法一步一步扩展半径就行了。

--------------------------------------------------------------------------------------------------------------------------

5、manacher算法代码如下:

char a[110010];//原串
char s[210100];//算法处理过后的字符串
int p[210030];//p[i]表示以字符t[i]为半径的回文子串长
int r;
void manacher()
{
    memset(p,0,sizeof(p));
    int l=strlen(a);
    r=2;
    s[0]='@';//防止越界
    s[1]='#';
    for(int i=0; i<l; i++)
    {
        s[r++]=a[i];
        s[r++]='#';
    }
    int mx=0,id=0;
    //mx表示某个回文串延伸在最右端半径的下标,id表示这个回文子串最中间位置下标
    //resLen表示对应在s中的最大子回文串的半径,resCenter表示最大子回文串的中间位置
    for(int i=1; i<r; i++)
    {
        p[i]=mx>i?min(mx-i,p[2*id-i]):1;
        //遇到三种特殊的情况,需要利用中心扩展法
        while(s[i+p[i]]==s[i-p[i]])
            p[i]++;
        //半径下标i+p[i]超过边界mx,需要更新
        if(i+p[i]>mx)
        {
            mx=i+p[i];
            id=i;
        }
        //更新最大回文子串的信息,半径及中间位置
        if(resLen<p[i])
        {
            resLen=p[i];
            resCenter=i;       
        }
    }
    int ans=0;//最长回文串长度
    for(int i=1; i<r; i++)
        ans=max(ans,p[i]-1);
    printf("%d\n",ans);
    //最长回文子串长度为半径-1,起始位置为中间位置减去半径再除以2
    printf("%d %d\n",(resCenter-resLen)/2,resLen-1);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值