manacher's Algorithm: 寻找最长回文串的O(n) 算法

这是一种 时间复杂度为 O(n) O ( n ) 的高效的求解字符串回文子串的动态规划算法。
网上文章一大堆,我主要参考 : 最长回文子串——Manacher 算法

他的思想如下:

  • 算法描述
  • code
  • 复杂度分析
  • 练习

算法描述

对一个字符串,str,(e.g: str = “agga”) 由于他的回文子串有两种情况,1是以字符串中字母为中心,即长度为奇数的串,2是以间隔为中心,即长度为偶数的串。因此先对其做一个填充:

str_pad = “#a#g#g#a#”, 这样回文串就全部以字符为中心了。

R[i] R [ i ] 以字符 str[i] s t r [ i ] 为中心的最长回文串的半径。显然(R[i]-1) 就是原来的回文串长度。

  1. 遍历 i=1:n i = 1 : n 计算所有的 R[i] R [ i ] ,
    mr m r 为当前遍历的字符串中回文串的最右端点, 同时记 zx z x 为当前最右端点的回文串的中心,即 (zxmr,zx+mr) ( z x − m r , z x + m r ) 一定是一个回文串。那么当前计算的点 i i 有两种情况:
    a. zx<i<mr z x < i < m r , 此时它关于 zx z x 的对称点就是 j=2zxi j = 2 z x − i 。由对称性: R[i]min{R[j],mri} R [ i ] ≥ m i n { R [ j ] , m r − i } 因此我们可以继续从此时的 R[i] R [ i ] 往后匹配.(并且仅有 R[j]>mri R [ j ] > m r − i 时才会往后匹配,即mr移动)
    b. i>=mr i >= m r , 此时就挨着匹配就好

code

上面的描述有点简短,建议结合文首文章中的图片来理解

char str[MAXN],pp[MAXN*2];
int R[MAXN*2]; // 回文串半径
int n,len;
void manacher(){
    // preprocess
    n = strlen(str+1);
    len=1;
    char pad = '#';
    pp[0] = '$';
    for(int i=1 ; i <= n ; ++i){
        pp[len++] = pad;
        pp[len++] = str[i];
    }
    pp[len++] = pad;
    ms(R,0);
    int mr=0,zx =0;//mr 刚好越过最右回文的点
    for(int i=1 ; i i? min(R[2*zx -i],mr -i) : 1;
        while (pp[i+R[i]]==pp[i-R[i]])++R[i];
        if(i+R[i]> mr){
            mr = i+R[i];
            zx = i;
        }
    }
}

复杂度分析

我们可以利用摊还分析,由于只有mr 移动才会开始有效匹配,因此, mr m r 移动次数就是 时间复杂度 O(n) O ( n )

练习

hihocoder #1799 : 基因合成
题解

你可能感兴趣的:(算法理论)