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

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

他的思想如下:

算法描述

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

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

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

  1. 遍历 i = 1 : n 计算所有的 R [ i ] ,
    m r 为当前遍历的字符串中回文串的最右端点, 同时记 z x 为当前最右端点的回文串的中心,即 ( z x m r , z x + m r ) 一定是一个回文串。那么当前计算的点 i 有两种情况:
    a. z x < i < m r , 此时它关于 z x 的对称点就是 j = 2 z x i 。由对称性: R [ i ] m i n { R [ j ] , m r i } 因此我们可以继续从此时的 R [ i ] 往后匹配.(并且仅有 R [ j ] > m r i 时才会往后匹配,即mr移动)
    b. 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<len ; ++i){
        R[i] = mr > 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 移动才会开始有效匹配,因此, m r 移动次数就是 时间复杂度 O ( n )

练习

hihocoder #1799 : 基因合成
题解

猜你喜欢

转载自blog.csdn.net/Dylan_Frank/article/details/81437170