• Manacher Algorithm 最长回文子串


    问题:求一个字符串的最长回文子串的长度

     http://hihocoder.com/problemset/problem/1032

    1.Brute Force

    枚举子串(起点和终点),再判断子串是否回文串。时间复杂度O(n^3)

    2.稍优的算法

    枚举子串的中点,从中点向两侧扩展判断回文串。时间复杂度O(n^2)

    3.Manacher算法

    在前一种算法的基础上再加以改进,达到O(n)的时间复杂度

    (1)处理偶数长度的子串

    首先考虑一个问题:长度为奇数的字符串的中心是中间那个字符的位置,而长度为偶数的字符串的中心是中间两个字符串之间的“缝隙”。枚举偶数长度的回文子串的中点,似乎不好处理(如何能枚举一个“缝隙”的位置呢?)

    既然这样,设法做一种预处理,在原字符串s的间隙插入特殊字符,例如'#'(包括首尾),得到新字符串t

      例如:

      s:    abcba

      t:     #a#b#c#b#a#

    这样,枚举到'#'就表示枚举到偶数长度的子串的中点了

    (2)辅助变量

    回文半径:回文串最右字符与回文中心字符的距离。以t[i]为中心的回文子串t[l,r],其回文半径为r-i+1

      例如:aba的回文半径为2

    辅助数组len:len[i]表示字符串t中以第i个字符为中心的回文子串的回文半径

      可知len[i]-1就是该回文子串在原字符串s中的长度

      证明:

      首先,在字符串t中,该回文子串长度是2*len[i]-1,以#开头且以#结尾;不妨先“去掉”开头或结尾的一个#,剩下的部分长度是2*len[i]-2,其中一半是原串s中的字符,一半是#,所以该子串在原串s中的长度就是len[i]-1

    辅助变量pos和maxRight:maxRight是当前已知的所有回文子串的右端点能延伸到的最大值,取得这个最大值的回文串的中点是pos

    (3)计算len数组

    从左往右依次计算len[i],设j是i关于pos的对称点,有(j+i)/2 = pos,j = 2*pos - i(j<i,故len[j]是已知的)

    讨论i的位置与maxRight的相对关系:

      当i<=maxRight:

      考虑len[j],若len[j] <= maxRight-i+1,则len[i] = len[j];否则len[i]的最小值是maxRight-i+1(从maxRight+1开始向右还可能再延伸以t[i]为中心的回文串长度)

      当i>maxRight:

      此时len[i]无法如前所述地利用之前已经求得的信息了,故只能从t[i]像左右扩展寻找最长回文长度

    (4)时间复杂度分析

    maxRight只会一直向右推移,i>maxRight时,对于一个字符t[i],它影响maxRight,使maxRight向右推移,此时我们进行了回文串的左右匹配过程;但是当i<maxRight时,就能直接利用之前以求得的信息,[0,maxRight]范围内的字符都暂时不再需要参与匹配了。所以字符串t中的每一个字符(作为右端字符)只会参与一次匹配,故时间复杂度为O(n)。(考虑某字符可能先被作为某子串的右端字符参与匹配,之后又作为另一子串的左端字符参与匹配,那么每个字符也最多只参与2次匹配,这仍然是线性的)

    4.代码实现

     1 int Manacher(string &s)
     2 {
     3     string t="#";
     4     for(int i = 0; i < s.size(); ++i)
     5         t += s[i], t += '#';
     6 
     7     int len[t.size()];
     8     int maxLen = 0;
     9     int pos = 0, maxRight = 0;
    10     for(int i = 0; i < t.size(); ++i)
    11     {
    12         len[i] = i<=maxRight? min(len[2*pos-i], maxRight-i+1) : 1;
    13         while(i-len[i]>=0 && i+len[i]<t.size() && t[i-len[i]]==t[i+len[i]])
    14             ++len[i];
    15         if(i+len[i] > maxRight)
    16             pos = i, maxRight = i+len[i]-1;
    17         maxLen=max(maxLen, len[i]-1);
    18     }
    19     return maxLen;
    20 }

    PS:如果要求最长回文串,那么在过程中记录下在字符串t中的maxLen对应的idx和len[idx]即可。(最长回文子串是t[idx-len[idx]+1, idx+len[idx]-1]中不为'#'的子序列组成的字符串)

  • 相关阅读:
    (转)堆与堆排序
    Cantor的数表
    Sticks(poj 1011)
    Square(hdu 1511)
    Fire Net(hdu 1045)
    Lake Counting(poj 2386)
    Ants (POJ 1852)
    A + B Problem II 大数加法
    深入理解计算机系统第二版家庭作业2.66
    C++ 队列queque/deque
  • 原文地址:https://www.cnblogs.com/junjie_x/p/7668714.html
Copyright © 2020-2023  润新知