Manacher算法核心
Manacher算法的核心部分在于它巧妙的令人惊叹的加速,这个加速一下把时间复杂度提升到了线性,让我们从暴力的算法中解脱出来,我们先引入概念,再说流程,最后提供实现代码。
概念:
ManacherString:经过Manacher预处理的字符串,以下的概念都是基于ManasherString产生的。
回文半径和回文直径:因为处理后回文字符串的长度一定是奇数,所以回文半径是包括回文中心在内的回文子串的一半的长度,回文直径则是回文半径的2倍减1。比如对于字符串 "aba",在字符 'b' 处的回文半径就是2,回文直径就是3。
最右回文边界R:在遍历字符串时,每个字符遍历出的最长回文子串都会有个右边界,而R则是所有已知右边界中最靠右的位置,也就是说R的值是只增不减的。
回文中心C:取得当前R的第一次更新时的回文中心。由此可见R和C时伴生的。
半径数组:这个数组记录了原字符串中每一个字符对应的最长回文半径。
流程:
步骤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<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
//算法主体
int maxLcsplength(string str) {
if (str.length() == 0) {
return 0;
}
int len = (int)(str.length() * 2 + 1);
char *chaArr = new char[len];
int* pArr = new int[len];
int index = 0;
for (int i = 0; i < len;i++) {
chaArr[i] = (i & 1) == 0 ? '#' : str[index++];
}
int R = -1;
int C = -1;
int maxn = 0;
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 (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) {
pArr[i]++;
}
else {
break;
}
}
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
maxn = max(maxn, pArr[i]);
}
//
delete[] chaArr;
delete[] pArr;
//重点
return maxn - 1;
}
Reference:https://www.cnblogs.com/cloudplankroader/p/10988844.html