首页 > 代码库 > 最长回文子串模板---Manacher算法。时间复杂度O(N)

最长回文子串模板---Manacher算法。时间复杂度O(N)

 1 #include<vector> 2 #include<iostream> 3 #include <algorithm> 4 #include <stdio.h> 5 #include <string.h> 6 using namespace std; 7  8 #define N 10001000 9 int n;10 int p[N];11 char s[N], str[N];12 13 #define _min(x, y) ((x)<(y)?(x):(y))14 15 void kp()16 {17     int i;18     int mx = 0;19     int id = 0;20     for(i=n; str[i]!=0; i++)21         str[i] = 0; //没有这一句有问题。。就过不了ural1297,比如数据:ababa aba22     for(i=1; i<n; i++)23     {24         if( mx > i )25             p[i] = _min( p[2*id-i], p[id]+id-i );26         else27             p[i] = 1;28         for(; str[i+p[i]] == str[i-p[i]]; p[i]++)29             ;30         if( p[i] + i > mx )31         {32             mx = p[i] + i;33             id = i;34         }35     }36 }37 38 void init()39 {40     int i;41     str[0] = $;42     str[1] = #;43     for(i=0; i<n; i++)44     {45         str[i*2+2] = s[i];46         str[i*2+3] = #;47     }48     n = n*2+2;49     s[n] = 0;50 }51 52 int main()53 {54     int i, ans;55     int T;56     scanf("%d",&T);57     while(T--)58     {59         scanf("%s", s);60         n = (int)strlen(s);61         init();62         kp();63         ans = 0;64         for(i=0; i<n; i++)65             if(p[i]>ans)66                 ans = p[i];67         printf("%d\n", ans-1);68     }69     return 0;70 }

 

这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。

    算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
    原串:    w aa bwsw f d
    新串:           #  w  # a # a #  b  # w # s # w #  f  # d #
辅助数组P: 1  2  1 2 3 2 1  2  1  2 1 4 1 2 1  2 1 2 1

这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。

现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。

    那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,其中id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。

    然后可以得到一个非常神奇的结论,这个算法的关键点就在这里了:如果mx > i,那么

P[i] >= MIN(P[2 * id - i], mx - i)。就是这个串卡了我非常久。实际上如果把它写得复杂一点,理解起来会简单很多:

 

//记j = 2 * id - i,也就是说 j 是 i 关于 id 的对称点。
if (mx - i > P[j]) 
    P[i] = P[j];
else /* P[j] >= mx - i */
    P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。


当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j]。

当 P[j] > mx - i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了。

由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)

 

转自:http://blog.163.com/zhaohai_1988/blog/static/2095100852012716105847112/

最长回文子串模板---Manacher算法。时间复杂度O(N)