Manacher’s Algorithm

Manacher算法详解

基本思想:对于字符串中每一个字符str[i],求出以它为中心的最大回文串的长度P[i],输出P[i]最大值对应的回文串

以字符串str="babcbabcbaccba"为例

Step 1 字符串预处理

在字符串两端以及每个字符间插入特殊字符#,然后在两端分别插入^$,构成新字符串 T="^#b#a#b#c#b#a#b#c#b#a#c#c#b#a#$"

插入#的目的是处理长度为偶数的回文串,例如"abccba",如果不插入字符,它的回文中心没有字符;插入^$的目的是省去边缘检测的步骤

Step 2 确定每个字符位置的回文串长度P[i]
0123456789101112131415161718192021222324252627282930
^#b#a#b#c#b#a#b#c#b#a#c#c#b#a#$
0010301070109010501010121010100

最大P[i]i=12P[i] = 9,因此最大回文子串为abcbabcba

为了简化计算P[i]的步骤,我们利用回文字串的中心对称性,维护L、C、R三个变量,可以分为两种情况

1、i关于C的对称字符i'对应的回文串未超出L,即i'-L = R - i < P[i']

P[i] = P[i'] = P[2 * C - i]

image

如上图,P[13]P[11]关于C对称,且T[11]对应的回文字串在L右边,因此P[13]=P[11]=1

2、i关于C的对称字符i​​对应的回文串超出L,即i​​L=RiP[i​​]

此时需要从逐个字符判断P[i]的值

  • 确定基础长度和起始字符位置:

    • 如果i>R,基础长度为Len=0,起始字符位置为Ind=i+1
    • 如果iR,基础长度为Len=Ri,起始字符位置为Ind=R+1
  • Ind开始逐个字符搜索

  • 如果T[i]对应的回文字串超出R,即Len>Ri,更新C、L、R

GET-LEN(T, i, R)
    if(i > R)
        Len = 0
        Ind = i + 1
    else
        Len = R - i
        Ind = R + 1
    while(T[Ind] == T[2 * i - Ind])
        Len = Len + 1
        Ind = Ind + 1
    if Len > R - i
        C = i
        L = C - Len
        R = C + Len

image

如上图,P[15]和P[7]关于C对称,但是T[11]对应的回文字串超出LL,因此P[15]P[7]=7

  • 由于i=15<20=R,基础长度Len = R - i = 5,起始位置Ind = R + 1 = 21
  • 第一次比较,T[21]T[2iInd]=T[9],检查结束,P[i] = Len = 5
  • Len=5=Ri,不需要更新C、LR
Step 3 找出PP中的最大值MaxLenMaxLen以及其位置MaxIndMaxInd,输出最长回文串

最长回文子串为str.substr((MaxInd - 1 - MaxLen)/2, MaxLen)

C++实现:
string preProcess(string str)
{
    int len = str.length();
    if (len == 0)
        return "^$";
    string res = "^";
    for (int i = 0; i < len; i++)
        res += "#" + str.substr(i, 1);
    res += "#$";
    return res;
}
string longestPalindrome(string s) {
    string T = preProcess(s);
    int len = T.length();
    vector<int> P(len);
    int C = 0, L = 0, R = 0;
    int MaxLen = 0, MaxInd;
    for (int i = 0; i < len; i++)
    {
        if (2 * C - i < 0 || 2 * C - i - P[2 * C - i] <= L)
        {
            int temp = R - i < 0? 0: R - i;
            int ind = i > R? i + 1: R + 1;
            while (ind < T.length() && 2 * i - ind >= 0 && T[ind] == T[2 * i - ind])
            {
                temp++;
                ind++;
            }
            P[i] = temp;
            if (temp > R - i)
            {
                C = i;
                L = C - temp;
                R = C + temp;
            }
        }
        else
            P[i] = P[2 * C - i];
        if (P[i] > MaxLen)
        {
            MaxLen = P[i];
            MaxInd = i;
        }
    }
    for (int i = 0; i < len; i++)
        cout << T[i];
    cout << endl;
    for (int i = 0; i < len; i++)
        cout << P[i];
    cout << endl;
    return s.substr((MaxInd - 1 - MaxLen) / 2, MaxLen);
}
时间复杂度分析

预处理部分,共需要循环N次,每次为O(1),总时间耗费为O(N)

计算PP时

  • 第一种情况时间耗费为O(1),第一种情况出现不会超过N次,即O(N);
  • 第二种情况,由于C,R,L动态变化,每个字符被比较的次数不会超过一次,因此总的比较次数不超过N次,即O(N)

综上所述,Manacher's Algorithm的时间复杂度为O(N) + O(N) + O(N) = O(N)

转载于:https://www.cnblogs.com/zhuqi7/p/5969887.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值