流程:
步骤1:预处理原字符串
先对原字符串进行预处理,预处理后得到一个新的字符串,这里我们称为S,为了更直观明了的让大家理解Manacher的流程操作,我们在下文的S中不显示特殊字符(这样并不影响结果)。
步骤2:R和C的初始值为-1,创建半径数组pArr
这里有点与概念相差的小偏差,就是R实际是最右边界位置的右一位。
步骤3:开始从下标 i = 0去遍历字符串S
分支1:i > R ,也就是i在R外,此时没有什么花里胡哨的方法,直接暴力匹配,此时记得看看C和R要不要更新。
分支2:i <= R,也就是i在R内,此时分三种情况,在讨论这三个情况前,我们先构建一个模型
L是当前R关于C的对称点,i'是i关于C的对称点,可知 i' = 2*C - i,并且我们会发现,i'的回文区域是我们已经求过的,从这里我们就可以开始判断是不是可以进行加速处理了
情况1:i'的回文区域在L-R的内部,此时i的回文直径与 i' 相同,我们可以直接得到i的回文半径,下面给出证明
红线部分是 i' 的回文区域,因为整个L-R就是一个回文串,回文中心是C,所以i形成的回文区域和i'形成的回文区域是关于C对称的。
情况2:i'的回文区域左边界超过了L,此时i的回文半径则是i到R,下面给出证明
首先我们设L点关于i'对称的点为L',R点关于i点对称的点为R',L的前一个字符为x,L’的后一个字符为y,k和z同理,此时我们知道L - L'是i'回文区域内的一段回文串,故可知R’ - R也是回文串,因为L - R是一个大回文串。所以我们得到了一系列关系,x = y,y = k,x != z,所以 k != z。这样就可以验证出i点的回文半径是i - R。
情况3:i' 的回文区域左边界恰好和L重合,此时i的回文半径最少是i到R,回文区域从R继续向外部匹配,下面给出证明
因为 i' 的回文左边界和L重合,所以已知的i的回文半径就和i'的一样了,我们设i的回文区域右边界的下一个字符是y,i的回文区域左边界的上一个字符是x,现在我们只需要从x和y的位置开始暴力匹配,看是否能把i的回文区域扩大即可。
总结一下,Manacher算法的具体流程就是先匹配 -> 通过判断i与R的关系进行不同的分支操作 -> 继续遍历直到遍历完整个字符串
时间复杂度:
我们可以计算出时间复杂度为何是线性的,分支一的情况下时间时间复杂度是O(n),分支二的前两种情况都是O(1),分支二的第三种情况,我们可能会出现O(1)——无法从R继续向后匹配,也可能出现O(n)——可以从R继续匹配,即使可以继续匹配,R的值也会增大,这样会影响到后续的遍历匹配复杂度,所以综合起来整个算法的时间复杂度就是线性的,也就是O(n)。
带注释代码
#include<bits/stdc++.h>
using namespace std;
int manacher(string str) {
if(str.size()==0) return 0;
int len=(int)(str.length()*2+1);//manacher字符串的长度
string ma_str;//manacher化的字符串
int parr[len];//记录每个位置的回文半径
for(int i=0;i<str.size();i++) ma_str=ma_str+'#'+str[i];
ma_str=ma_str+'#';
cout<<ma_str<<endl;
int R=-1;//最右回文边界
int C=-1;//R对应的最左回文中心
int maxn=0;//maxn是记录的最大回文半径
for (int i=0;i<len;i++){
//第一步直接取得可能的最短的回文半径,当i>R时,最短的回文半径是1,
//反之,最短的回文半径可能是i对应的i'的回文半径或者i到R的距离
//取最小值后开始从边界暴力匹配,匹配失败就直接退出
parr[i]= R>i ? min(R-i,parr[2*C-i]) : 1;
while( i+parr[i]<len && i-parr[i]>-1 ){
if(ma_str[i+parr[i]]==ma_str[i-parr[i]])parr[i]++;
else break;
}
if(i+parr[i]>R){//观察此时R和C是否能够更新
R=i+parr[i];
C=i;
}
maxn=max(maxn,parr[i]);//更新最大回文半径的值
}
return maxn-1;
//ma_str的长度和原字符串不同,得到的最大回文半径其实是原字符串的最大回文子串长度加1
}
压行纯代码
#include<bits/stdc++.h>
using namespace std;
int manacher(string str) {
if(str.size()==0) return 0;
int len=(int)(str.size()*2+1);
int parr[len],R=-1,C=-1,maxn=0;
string ma_str;
for(int i=0;i<str.size();i++)
ma_str=ma_str+'#'+str[i];
ma_str=ma_str+'#';
cout<<ma_str<<endl;
for(int i=0;i<len;i++){
parr[i]= R>i ? min(R-i,parr[2*C-i]) : 1;
while( i+parr[i]<len && i-parr[i]>-1 ){
if(ma_str[i+parr[i]]==ma_str[i-parr[i]])parr[i]++;
else break;
}
if(i+parr[i]>R){
R=i+parr[i];
C=i;
}
maxn=max(maxn,parr[i]);
}
return maxn-1;
}