O(n)时间求 最长回文子串 Manacher算法

  1. 回文字符串分为奇回文和偶回文,在字符串中间插入任意字符使得串变成奇回文串

暴力思想:肯定是找一个点往两边任意扩展,遍历一次,
Manacher时间为O(n)

  1. 开一个数组p[]记录 以点i为中点 的最长回文串的半径,
  2. 假设前i-1个点的p[]都已经求出来来,现在考虑p[i]如何推导
    网上找来的图片

重点:
3. Rp[1]~p[i-1]中推出来的右端最远最长回文子串的右端点,且用pos记录下来,每一步更新pos+R,既然R知道了,L也知道(没啥意义)
4. 当前遍历到i点,j是与i对称的点,且 $j=pos-(i-pos)$ -> $j=2pos-i$,我们可以通过j点来得到i点的p[i]
5. 此时i j R 有三种情况
a. 第一个图中红色都在[L,R]范围内,R-i>p[j],直接得到p[i]的值,p[i]=p[j]
b.

此时j点的回文子串只有一部分在[L,R](最大的回文子串)中,那么超出范围的肯定不符合要求,R-i<p[j]所以p[i]=R-i
c. 剩下一个情况应该都看出来了,就是i>=R,现在只能往两边暴力扩展了

总结步骤:

  1. 先更新p[],有三种情况
  2. 向两边暴力扩展回文字符串,满足前两种情况这一步肯定扩展不出来新的回文串,所以不需要特殊判断是否if(p[i]==1),所以这一步往两边扩展
  3. 更新[L,R]右端最远的回文子串,并且记录pos的值,
  4. 这是(插入字符的奇数字符串)上的一系列操作,我们要转化成源字符串上的位置长度 这里是2个结论,自己对比就可以知道了
    原字符串的位置:index = (resid - reslen)/2
    源字符串的长度:len = reslen-1
  • 在第一个添加一个与其他都不同的字符,就是为了index正确求出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string t;
while ( cin>>t && t!="END" ) {
string s("%#");
for (int i=0; i<t.size(); i++) {
s += t[i];
s += "#";
}
vector<int> p(s.size(),0);
int id=0, mx=0, resid=0, reslen=0;
for (int i=1; i<s.size(); i++) {
p[i] = mx > i ? min(p[2*id-i],mx-i):1;
while ( s[i-p[i]] == s[i+p[i]] ) ++p[i];
if ( mx<i+p[i] ) {
mx = i+p[i];
id = i;
}
if ( reslen < p[i] ) {
reslen = p[i];
resid = i;
}
}
string ans=t.substr( (resid-reslen)/2 , reslen-1 );//求出来的最长回文子串
cout << ans.size() << endl;//长度
}
return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!