最长回文子串(Manacher算法)

来源:互联网 发布:机顶盒的电视直播软件 编辑:程序博客网 时间:2024/06/08 17:36

本总结是是个人为防止遗忘而作,不得转载和商用。

 

题目

         给定字符串str,若子串s是回文串,称s为str的回文子串。设计算法,计算str的最长回文子串。

Manacher算法

         因为回文串有奇数和偶数的不同。判断一个串是否是回文串,往往要分开编写,造成代码的拖沓。

         为了解决这个问题,就采用下面的做法:

                   因此长度为n的字符串,共有n-1个“空”(gap),如:字符串abc,a和b之间有个“空”,b和c之间有个“空”,如果用“#”表示“空”的话,那字符串就可以写成:a#b#c

                   而如果在把首字母前面和末字符后面的“空”也算上的话,那字符串和“空”一起就一共有2n+1个,必定是奇数。

                   这里将类似:#a#b#c#的串称作gap串。

         因此,将待计算母串扩展成gap串,计算回文子串的过程中,只考虑奇数匹配即可。

         最后,为了处理统一,在最前面假设一个字符串中未出线的字符,如$,并用一个数组P[i]记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i]),比如S和P的对应关系,如下:

                  字符串     :12212321  ->  S[]= “$ # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #”

                   字符串和P[i]:

                            S[]=  # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #

                            P[] =  1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1

                   对于S[i=8] = 1,其回文最长向左4个向右4个(包括自己),所以将P[8] = 4。

                   又因为P[i] - 1正好是原字符串中回文串的总长度,所以之后只需要遍历遍历P,并找到最大的就可以了。

Tip:

    因为P中当i为奇数时对应的S都是#,所以只需要计算P[i]为偶数的情况。


         下面求P[i] 。

                 PS:假设P[0 … i-1]已经算出来,现在想求P[i]。                

        

         如上图所示。

         对于:

                   S[]=  # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #

                   P[]=  1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1

         因为S[i + P[i]]正好是“以i为中心的回文子串的下一个字符”,即:S[i+P[i]] 的前一个字符还属于S[i] 的回文子串,所以i+P[i]就很重要。

         又因为P[0 … i-1]都已经算出来了,所以我们就可以找到S[0]~S[i-1] 的i+P[i] 的最大值,这里将其记做mx。

         假设mx = id+P[id]。

         为了知道以id为中心的回文子串,这里我们记mx关于id的对称位置是my,同理i关于id的对称点是j。

         话说,我们已经算出了P[0]~ P[i-1],再加上i和j在以id为中心的回文子串范围内,所以P[i] = P[j]。

         而如果以j为中心的回文子串左侧超过my,则至少我知道“以j为中心左到my长度,右同样长度的回文子串”和“以i为中心的左右同样长度的回文子串”的长度是相等的,所以先将p[j] 赋值成j-my,至于剩下的部分,单独算就好。

         综上:

                   如果p[j] > j- my:则P[i] = j - my = mx - i

                   如果P[j] < j- my:则P[i] = P[j] = P[2*id - i]

         于是:

                   P[i] = min(P[2*id - i], mx - i)

         注意:上面的公式的前提是i<mx,如果i>mx,则只能把P[i]初始化为1了。

         写成代码的话就是:

                   if(mx > i ) {

                            P[i]= min(P[2*id - i], mx - i);

                   }else {

                            P[i]= 1;

                   }

         然后看看在此基础上以S[i]为中心的回文串能不能在扩展扩展,于是再添加一行代码:

                   for(;S[i+P[i]] == S[i-P[i]]; P[i]++);

         上面就算出了以S[i]为中心的回文子串,接下来:

                   如果mx <i+P[i]:

                            就更新mx,即mx = i +P[i]

                            并令id = i

         然后不停的做上面的事情,将i从头遍历到尾,就OK了。

         哦,对了,补充下初始化的值:

                   P[0]= 1, id = 0, mx = 1

时间复杂度

         O(N)

0 0
原创粉丝点击