浅谈Manacher算法

1. 前言

给定一个长度为 n n n 的字符串 S,Manacher 算法可以在 O ( n ) O(n) O(n) 的时间内计算出分别以 S 中的每个字符为中心,最大回文子串的长度. 通常 Manacher 算法可以用来求解一个字符串中的最长回文子串,或者是统计一个字符串中所有回文子串的个数等.

2. 字符串预处理

首先需要对带求解的字符串 S 填充分隔符,在每个字符的两边都插入一个特殊的符号,这里我们取 # 作为分隔符,比如 ABCD 填充之后变成了 #A#B#C#D#;再比如 ABC 填充之后变成了 #A#B#C# . 这样做的好处是能够让填充后的字符串长度始终为奇数.

3. 算法过程

令数组 P[i] 记录以字符 S[i] 为中心的最长回文子串向左右扩展的长度(包含 S[i]),问题的核心就是求解出所有的 P[i],以下图为例.

可以看出,P[i]-1正好是原字符串中以 S[i] 为中心的最长回文串长度.

Manacher 算法采用递推的方式计算 P[i],也就是说在计算 P[i] 时,P[0],P[1],…,P[i-1] 都已经计算完毕了.

具体计算 P[i] 时,该算法使用两个辅助变量 idmx,其中 id 为 P[0],P[1],…,P[i-1] 中右边界最大的回文子串的中心下标,mx=id+P[id],也就是这个子串的右边界( S[mx] 并不在以 id 为中心的最长回文串中). 令 j=2*id-i ,也就是说 ji 关于中心 id 的对称点,开始分类讨论:

  • i<mx

    • P[j]<mx-i,那么以 S[j] 为中心的回文子串完全被包含在以 S[id] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的回文子串也同样会完全被包含在以 S[id] 为中心的回文子串中,以 S[j] 为中心的最长回文子串和以 S[i] 为中心的最长回文子串完全相同,所以必然有 P[i] = P[j],如下图.
    • P[j]>=mx-i,以 S[j] 为中心的回文子串不一定完全被包含于以 S[id] 为中心的回文子串中,但是根据回文串的对称性,下图中两个绿框所包围的部分是相同的,也就是说以 S[i] 为中心的回文子串,其向右至少会扩张到 mx 的位置,也就是说 P[i]>=mx-i,至于 mx 之后的部分是否对称,需要继续暴力匹配.
  • i>=mx:以 i 为中心,暴力向左右匹配.

const int maxn=1e5+5;
int p[maxn<<1];//数组开两倍大小

string init(string s){
    string res="#";
    for(char ch:s){
        res+=ch;
        res+='#';
    }
    return res;
}

void manacher(string s){
    int len=s.length();
    int id=0,mx=0;
    for(int i=0;i<len;++i){
        p[i]=i<mx?min(p[2*id-i],mx-i):1;
        while(i-p[i]>=0 && i+p[i]<len && s[i+p[i]]==s[i-p[i]]) ++p[i];
        if(i+p[i]>mx){
            mx=i+p[i];
            id=i;
        }
    }
}

4. 总结

对于字符串中的每一个位置,只进行一次匹配,Manacher 算法的总体时间复杂度为 O ( n ) O(n) O(n),n 为字符串的长度,填充分隔符后长度是原来的两倍,所以时间复杂度依然是线性的.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值