• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
ZZZZZQ
博客园    首页    新随笔    联系   管理    订阅  订阅

Manacher’s Algorithm

基本思想:对于字符串中每一个字符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 # $
0 0 1 0 3 0 1 0 7 0 1 0 9 0 1 0 5 0 1 0 1 0 1 2 1 0 1 0 1 0 0

最大P[i]为i=12时P[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=R−i≥P[i​′​​]

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

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

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

  • 如果T[i]对应的回文字串超出R,即Len>R−i,更新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[2∗i−Ind]=T[9],检查结束,P[i] = Len = 5
  • Len=5=R−i,不需要更新C、L、R

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)

posted @ 2016-10-17 15:15  ZZZZZQ  阅读(162)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3