Manacher算法

Manacher算法是一种用于查找字符串中最长回文子串的高效算法,它的时间复杂度为O(n)

算法维护一个数组P,其中P[i]表示以位置i为中心的最长回文串的半径
Manacher算法利用了回文串的对称性来加速搜索过程。如果当前考虑的位置i在之前找到的最长回文串的范围内,那么可以利用这个回文串的对称性来确定i的位置的回文半径的初始值。如果i的位置超出了之前回文串的范围,那么需要从i的位置开始进行暴力匹配来确定回文半径
在算法执行过程中,会维护一个最右端点R,它表示当前找到的最长回文串的右端点。每次找到一个新的回文串时,都会更新R的位置

Manacher 算法可以计算以s[i](或者s[i] 和 s[i+1])为回文中心的最长回文子串的长度。

此外,还可以:

  • 判断任意子串是否为回文串。
  • 计算从𝑠[𝑖] 开始的最长回文子串的长度。
  • 计算以𝑠[𝑖] 结尾的最长回文子串的长度。
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
32
class Manacher {
public int[] p; // 用于存储每个位置的回文半径
private String s; // 原始字符串
private char[] t; // 预处理后的字符串,用于简化回文搜索
public Manacher(String s) {
this.s = s;
preprocess();
p = new int[t.length];
int center = 0, right = 0; // 初始化最右窗口的中心点和最右端点
for (int i = 1; i < t.length-1; i++) {
int mirror = 2*center - i; // 当前位置i关于center的对称位置
if (right > i) // 如果当前位置i在已知回文串的范围内,则可以利用对称性来初始化p[i]
p[i] = Math.min(right - i, p[mirror]);
while (t[i + (1 + p[i])] == t[i - (1 + p[i])]) // 超出最右窗口范围,暴力扩展回文串
p[i]++;
if (i + p[i] > right) { // 如果当前回文串窗口的右端点超过了最右端点,则更新窗口center和right
center = i;
right = i + p[i];
}
}
}
private void preprocess() { // 改造原始字符串,避免讨论奇偶性,s[i:j] -> t[2*i+2, 2*j+2],这样不管s字串的长度是奇数还是偶数,映射到t的长度都是奇数(左右闭)
t = new char[s.length()*2 + 3];
t[0] = '$';
t[s.length()*2 + 2] = '@';
for (int i = 0; i < s.length(); i++) {
t[2*i + 1] = '#';
t[2*i + 2] = s.charAt(i);
}
t[s.length()*2 + 1] = '#';
}
}

无注释模板

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
32
class Manacher {
public int[] p;
private String s;
private char[] t;
public Manacher(String s) {
this.s = s;
preprocess();
p = new int[t.length];
int center = 0, right = 0;
for (int i = 1; i < t.length-1; i++) {
int mirror = 2*center - i;
if (right > i)
p[i] = Math.min(right - i, p[mirror]);
while (t[i + (1 + p[i])] == t[i - (1 + p[i])])
p[i]++;
if (i + p[i] > right) {
center = i;
right = i + p[i];
}
}
}
private void preprocess() {
t = new char[s.length()*2 + 3];
t[0] = '$';
t[s.length()*2 + 2] = '@';
for (int i = 0; i < s.length(); i++) {
t[2*i + 1] = '#';
t[2*i + 2] = s.charAt(i);
}
t[s.length()*2 + 1] = '#';
}
}