Manacher算法简介:
1.作用:Manacher算法又名马拉车算法,用来求一个字符串中最长回文子串的长度。
2.复杂度分析:时间复杂度为O(n)。
算法核心思想:
1.伪代码:假设str为待判断的字符串,len[ i ]数组存放以该 str[ i ] 字符为中心的最长回文子串的长度,mid为当前最长回文子串的中点,mx为当前最长回文子串的右边界,那么对当前位置 i 有如下伪代码:(此时以知道前 i - 1个字符中最长回文子串的长度以及以每个字符为中心的最长回文串的长度)
- 若 i < mx,则 len[ i ] = min ( len[ 2*mid - i ] , mx - i )。
- 否则len[ i ] = 1。
- 对 i 位置上的字符向两边进行匹配,更新len[ i ]的值,更新结束后更新mid和mx的值。
2.算法思路:为何能按照这样步奏执行算法呢?
2.1 i < mx :
首先,如果 i < mx,说明字符 str[ i ] 在以mid为中心的回文串中,那么其与以 str[ j ]( i 关于 mid 的对称位置)为中心的最长回文串有关。而以 str[ j ]字符为中心的最长回文串有三种可能:
- 一种是该串(以j为中心的最长回文串)仍在以mid为中心的最长回文串中
- 一种是超出
- 最后一种是刚好相等
我们可以证明第一种与第二种情况下,以 str[ i ] 为中心的最长回文串的长度为len[ j ] 或 mx - i。[ 注释1 ]而第三种情况下,以str[ i ] 为中心的回文串长度却可能大于len[ j ],故需要走伪代码中的第3步来更新len[ i ] 。
2.2 i >= mx :
而如果i >= mx 情况下,说明以 i 为中心的最长回文串无法从之前得到的len[ 1 ~ i -1]中得知,只能先令len[ i ] = 1,再分别向两边拓展,判断回文长度了,故走伪代码中2、3步。
注释1:证明第一、第二中情况下以str[ i ]为中心的最长回文串的长度为len[ j ] 或 mx - i。
- 若以str[ j ]为中心的最长回文串仍在以str[ mid ]为中心的回文串中,那么由于 i 与 j 关于mid对称,如下图,a != b,而d = a,c = b,故c != d,故len[ i ] = len[ j ]。
- 若以str[ j ] 为中心的最长回文串超过了以str[ mid ]为中心的最长回文串,同理可证明以str[ i ] 为中心的回文串不可能与以str[ j] 为中心的最长回文串相等。
模板
//Manacher
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN = 1e5;
char str[MAXN];
char tmp[2*MAXN];
int len[2*MAXN];
int Manacher(char str[]){
tmp[0] = '$';
tmp[1] = '#';
int str_len = strlen(str);
for(int i = 1;i <= str_len;i++){
tmp[2*i] = str[i-1];
tmp[2*i+1] = '#';
}
tmp[2*str_len+2] = ' ';
//cout << tmp << endl;
int mx = 0;
int maxlen = -1;
int mid;
for(int i = 1; tmp[i]; i++){
if(i < mx) len[i] = min(len[2*mid-i],mx-i);
else len[i] = 1;
while(tmp[i-len[i]] == tmp[i+len[i]]) len[i]++;
if(len[i]+i > mx){
mx = len[i]+i;
mid = i;
}
maxlen = max(maxlen,len[i]-1);
}
return maxlen;
}
int main(){
scanf("%s",str);
cout << Manacher(str);
return 0;
}