文章目录
求解回文字符串时,可以通过暴力匹配法进行匹配,即每次移动一个字符,使用中心扩展法,计算最长回文字符串。这种匹配方式时O(n^2)复杂度。
Manacher算法可以以O(n)复杂度计算最长回文字符串。
一、Manacher算法流程
1.字符串扩充
由于字符串长度可能时偶数,可能时奇数,所以需要先对其进行统一。
Manacher的做法是:将任意字符间以及字符串开头和结尾增添一个标记字符,使得字符串长度都为奇数
比如
字符串为"abcdf"
扩充后的字符串为:“#a#b#c#d#f#”
2.回文字符串半径求解
回文半径数组radius是用来记录以每个位置的字符为回文中心求出的回文半径长度,如下图所示,对于p1所指的位置radius[6]的回文半径是5,每个位置的回文半径组成的数组就是回文数组,所以#a#c#b#b#c#b#d#s#的回文半径数组为[1, 2, 1, 2, 1, 2, 5, 2, 1, 4, 1, 2, 1, 2, 1, 2, 1]。
3.最右回文右边界R求解
一个位置最右回文右边界指的是这个位置及之前的位置的回文子串,所到达的最右边的地方。比如对于字符串#a#c#b#b#c#b#d#s#,求它的每个位置的过程如下:
也就是说字符串中下标i对应的最右回文右边界为前i个字符所能获得的右边界的最大值
4.最右回文右边界的对称中心C
就是上面提到的最右回文右边界的中心点C,如下图,p=4时,R=6,C=3
5.Manacher算法流程
图中:
1)MLC数组是以下标i为回文中心,能得到的回文字符串的最长半径
2)before数组是未扩充时的下标和对应元素
3)after数组时扩充后的字符串下标和对应元素
4)POS表示的是下标。POS(before)表示的是before数组中下标,同理POS(after)是扩充后的下标
5)START是回文字符串的起点
6)END是回文字符串终点
6.Manacher算法代码
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
string t = "$#";
for (const char &c: s) {
t += c;
t += '#';
}
n = t.size();
t += '!';
auto f = vector <int> (n);
int iMax = 0, rMax = 0, ans = 0;
for (int i = 1; i < n; ++i) {
// 初始化 f[i]
f[i] = (i <= rMax) ? min(rMax - i + 1, f[2 * iMax - i]) : 1;
// 中心拓展
while (t[i + f[i]] == t[i - f[i]]) ++f[i];
// 动态维护 iMax 和 rMax
if (i + f[i] - 1 > rMax) {
iMax = i;
rMax = i + f[i] - 1;
}
// 统计答案, 当前贡献为 (f[i] - 1) / 2 上取整
ans += (f[i] / 2);
}
二、KMP算法流程
KMP是用于进行字符串匹配的
下面的链接讲的比较详细,就不抄录
https://zhuanlan.zhihu.com/p/83334559