Manacher算法:是一种高效的找出一个字符串中回文序列的最大值的算法。时间复杂度为O(n);
我们正常的对于字符串的回文串的求法。是对从i向两端延伸。时间复杂度过不去很多题。所以manacher算法的优势就要展现了。
我们可以思考一个问题我们是否能不用每次都从i点来向两边扩展。答案是可以的。我们可以利用前面算好的回文串长度来优化当前回文串长度的查找。
在详细讲这个算法之前。来明晰一些要用上数据结构,以及其含义。
p【i】:代表以i为中点的回文串长度。
mx:当前回文串的最右端点的位置。
id:当前最靠右端回文串的中点位置。
好了,上面的概念不理解没有关系,但是要记住,他们各自都代表什么。
Manacher算法的第一步:对原字符串的预处理。就是在字符中间添加#号。具体这一步的用法是用来解决单双数字符串的问题。举个例子就能明白了。
例如:对于字符串 aba来说,经过处理后将会变成@#a#b#a#。字符串个数为奇数。
对于字符串 abab来说,经过处理后将会变成@#a#b#a#b#。字符串个数为奇数。
细心地同学会发现。为什么前面有个@呢。让s【0】 = ‘@’。的话会方便字符串下标的处理。
Mancher算法的第二步:遍历数组。对于任意i位置。我们要取得i关于id对称的位置j。因为id是半径长度为mx-id的回文串。所以i位置的回文情况是和关于id对称点j的回文情况是相等的。举个例子
@ # b # a # b # a # e # a # b # a # b #;
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
j id i mx
id-(i-id)
p【】 3 3
当i = 14
p【i】 = p【id-(i-id)】= 3;
但是这只是其中的一种情况而已。
再举一个例子:
@ # e # a # b # a # e # a # b # a # d #
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
j id i mx
id-(i-id)
p【】 5 3
为什么会出现这种情况的呢?
我们知道对于这个例子当i = 14时,当前最长的回文串时以id为中心半径长度为mx - id。所以关于e这个字符两边的aba这一段一定是相等。但是不能保证左边的以b为中心的回文串的长度只到两边的a。这个回文串有可能更长。这样就导致左边的b中p【j】的值不等于p【i】。但是我们可以知道的是两段aba是一定相等的。
所以综合这两种情况。我们可以得出
p【i】 = min(mx-i ,p【id-(i - id)】);
当然了这是在mx > i 的情况
不在这个情况的下
p【i】 = 1;
manacher算法到这里就完成了?naive 还有一步。就是再向两边来找,也就是去延伸i点的回文串。然后比较 更新 id 和mx的值。
下面看一下具体实现过程:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20000005;
char str[N];
int p[N];
void
manacher(char *s,int len){
p[0] = 1;
int mx = 0 , id = 0;
for(int i = 1 ;i < len ; i ++){
p[i] = mx > i ? min(p[id*2 - i],mx-i) : 1;
while(s[i+p[i]] == s[i-p[i]])
p[i] ++;
if(i+p[i] > id + p[id]){
id = i;
mx = i +p[i];
}
}
}
int main(){
while(scanf("%s",str)){
int len = strlen(str);
for(int i = len ; i >= 0; i --){
str[(i << 1) + 1] = '#';
str[(i << 1) + 2] = str[i];
}
str[0] = '@';
len = len*2 +2;
manacher(str,len);
int ans = 0;
for(int i = 0 ; i < len ; i++)
ans = max(ans,p[i]-1);
printf("%d\n",ans);
}
return 0;
}