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]中不为'#'的子序列组成的字符串)

原文地址:https://www.cnblogs.com/junjie_x/p/7668714.html