Manacher算法总结

一、用途:

给一个字符串,求它的最长回文子串;比如:
s = "abbacbca",最长回文子串为 "acbca",长度为 5 5
如果用暴力的算法,枚举对称轴,向两边延伸;复杂度高达 O(n2) O ( n 2 ) !
有个叫 Manacher 的人发明了一种算法,可以 O(n) O ( n ) 的求出最长回文子串,就叫 Manacher 算法(俗称 马拉车算法);

二、算法详情:

2.1 预处理:

回文串分为 奇回文串(如 "acbca") 和 偶回文串(如 "abba"),处理的时候判奇偶是一件很麻烦的事,所以用一个小技巧对原串进行预处理:
在头尾以及两两字符中间都插入一个无关字符 (没有出现在这个串中的字符);
举个例子:"abcd" 填充之后 变为 "#a#b#c#d#";只要是无关字符都可以用来填充;
偶回文串 "abba" 处理后变为 "#a#b#b#a#",奇回文串 "acbca" 处理后变为 "#a#c#b#c#a#",长度都变成了奇数

2.2 p[] 的定义:

首先定义一个 p p 数组:
p[i] p [ i ] 表示以 i i 为中心的回文串的最大回文半径。
比如:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
New_s @ # a # b # b # a # c # b # c # a #
p[i] 1 2 1 2 5 2 1 2 1 2 1 6 1 2 1 2 1

可以看出, max(p[i]1) m a x ( p [ i ] − 1 ) 就是原串的最长回文子串的长度;

2.3 求解p[]

假设我们正在求 p[i] p [ i ] ,即 p[1,,i1] p [ 1 , ⋯ , i − 1 ] 都已求出;
新增两个变量 Mr 和 Mid,定义如下:
Mr : 中心在 i i 之前的所有回文子串,所能延伸至的最右端的位置;
Mid : 右端延伸至 Mr 处的回文子串的中心位置
Mid + p[Mid] = Mr

假设变量的相对位置如图:
(1) if (i < Mr)
即以 Mid 为中心的回文串为黑色的那段覆盖了红色的两段,根据回文串的性质,有 以 j 为中心 的回文串 和 以 i 为中心 的回文串相等,即图中红色两段相等;
既然这样,就不用以 i i 为中心向两边延伸了,直接 p[i] = p[j] ,加速查找;
Manacher算法总结_第1张图片
(2) else
i i 在 Mr 右边,这种情况,只能老老实实向两边延伸了;
Manacher算法总结_第2张图片

2.4 参考代码:
/**********************************************
 *Author*        :XzzF
 *Created Time*  : 2018/4/12 16:42:30
 *Ended  Time*  : 2018/4/12 16:57:48
*********************************************/

#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 110005;

char s[MaxN + 5];
char New_s[2 * MaxN + 5];  //填充字符之后的串
int p[2 * MaxN + 5];  //p[i]表示以i为中心的最长回文串半径

int Get_New() {
    int len = strlen(s);
    New_s[0] = '@'; New_s[1] = '#';
    int L = 1;
    for(int i = 0; i < len; i++) {
        New_s[++L] = s[i];
        New_s[++L] = '#';
    }
    New_s[L + 1] = '\0';   //不要忘了
    return L;
}

int Manacher() {
    int len = Get_New();
    int Max_len = 0;
    int Mr = 0;       //遍历过的所有回文串中,所能到达的最右端的位置
    int Mid = 0;       //能到达最右端位置的回文串的中心位置
    for(int i = 1; i <= len; i++) {
        if(i < Mr) 
            p[i] = min(p[2 * Mid - i], Mr - i);
        else p[i] = 1;

        //不需边界判断,因为左有'@',右有'\0'
        while(New_s[i - p[i]] == New_s[i + p[i]])
            p[i]++;

        //更新Mr
        if(Mr < i + p[i]) {
            Mid = i;
            Mr = i + p[i];
        }
        Max_len = max(Max_len, p[i] - 1);
    }
    return Max_len;
}

int main()
{
    while(scanf("%s", s) != EOF)
    {
        printf("%d\n", Manacher());
    }
    return 0;
}

算法复杂度分析:

Manacher算法总结_第3张图片
Manacher算法总结_第4张图片
Manacher算法总结_第5张图片

你可能感兴趣的:(字符串)