Manacher 算法(马拉车算法)能在 O(n) 时间内求出字符串中每个位置的最长回文半径,从而找到最长回文子串。相比中心扩展的 O(n²),它是回文问题的终极解法。
加载中…
应用场景:最长回文子串
Manacher 算法(马拉车算法)能在 O(n) 时间内求出字符串中每个位置的最长回文半径,从而找到最长回文子串。相比中心扩展的 O(n²),它是回文问题的终极解法。
交互式动画演示,可调整参数并单步执行。
全屏打开graph LR A[字符串] --> B[插#统一奇偶] B --> C[利用对称半径] C --> D[O(n) 求所有回文]
#,统一奇偶长度回文
"aba" → "#a#b#a#"(长度 2n+1)对于位置 i:
i < R:利用对称点 mirror = 2*C - i 的已知半径初始化C = i, R = i + p[i]public class Manacher {
public static String longestPalindrome(String s) {
// 1. 预处理:插入分隔符
StringBuilder sb = new StringBuilder("^#");
for (char c : s.toCharArray()) {
sb.append(c).append('#');
}
sb.append('$');
char[] t = sb.toString().toCharArray();
int n = t.length;
int[] p = new int[n]; // p[i] = 以 i 为中心的回文半径
int C = 0, R = 0; // 当前最右回文的中心和右边界
for (int i = 1; i < n - 1; i++) {
int mirror = 2 * C - i;
// 利用对称性初始化
if (i < R) {
p[i] = Math.min(R - i, p[mirror]);
}
// 尝试扩展
while (t[i + p[i] + 1] == t[i - p[i] - 1]) {
p[i]++;
}
// 更新最右边界
if (i + p[i] > R) {
C = i;
R = i + p[i];
}
}
// 找最大回文半径
int maxLen = 0, center = 0;
for (int i = 1; i < n - 1; i++) {
if (p[i] > maxLen) {
maxLen = p[i];
center = i;
}
}
// 映射回原字符串
int start = (center - maxLen) / 2;
return s.substring(start, start + maxLen);
}
public static void main(String[] args) {
System.out.println(longestPalindrome("babad")); // "bab" 或 "aba"
System.out.println(longestPalindrome("cbbd")); // "bb"
}
}虽然有两层循环(外层遍历 + 内层扩展),但:
均摊 O(n)。
| 指标 | 值 |
|---|---|
| 时间 | O(n) |
| 空间 | O(n)(p 数组 + 预处理字符串) |
| 方法 | 时间 | 空间 | 适用场景 |
|---|---|---|---|
| 暴力枚举 | O(n³) | O(1) | 不推荐 |
| 中心扩展 | O(n²) | O(1) | 面试首选,代码简洁 |
| 动态规划 | O(n²) | O(n²) | 需要所有子串信息 |
| Manacher | O(n) | O(n) | 竞赛/超长字符串 |
以 s = "babab" 为例,预处理后 t = "#b#a#b#a#b#":
t: # b # a # b # a # d #
i: 0 1 2 3 4 5 6 7 8 9 10
p[i]: 0 1 0 3 0 5 0 3 0 1 0
关键步骤(维护 center=5, right=10 后):
i=6: i < right,镜像 j = 2*5-6 = 4,p[4]=0
从 p[6]=0 开始扩展:t[5]='b' vs t[7]='a' 不等 → p[6]=0
i=7: i < right,镜像 j = 3,p[3]=3
但 right - i = 3 = p[3],不能直接取 → 从 p[7]=3 继续扩展
t[4]='#' vs t[10]='#' 相等 → p[7]=4? 不对,t[3]...
实际:p[7] 先取 min(p[3], right-i)=3,再尝试扩展
核心规则:
i < right: p[i] = min(p[2*center - i], right - i),然后尝试继续扩展
i >= right: p[i] = 0,直接扩展
扩展后若 i + p[i] > right,更新 center=i, right=i+p[i]
p[i] 的含义:以 t[i] 为中心的回文半径。原串中以 i 为中心的最长回文长度 = p[i]。
| 题目 | 难度 | 核心思路 |
|---|---|---|
| 最长回文子串(LC 5) | 🟡 Medium | 中心扩展 or Manacher |
| 回文子串计数(LC 647) | 🟡 Medium | 中心扩展计数 or p数组求和 |
| 分割回文串(LC 131) | 🟡 Medium | 回溯 + 回文判断 |
| 最短回文串(LC 214) | 🔴 Hard | KMP 前缀函数 or Manacher |
| 最长回文子序列(LC 516) | 🟡 Medium | 区间 DP(非 Manacher) |
1. 预处理插入分隔符的原因
"aba" (奇数长) 和 "abba" (偶数长) 的回文中心不同:
奇数:中心在字符上;偶数:中心在两字符之间
插入 '#' 后统一为奇数长度:"#a#b#a#" 中心总在某个字符上
2. 镜像回文超出左边界的情况
如果镜像回文超出了当前大回文的左边界,
超出部分无法保证对称,必须逐字符重新验证
3. 下标转换错误
// t 中下标 i → 原串下标 (i - p[i]) / 2
// 最长回文子串起始位置 = (center - p[center]) / 2
练习推荐:先用中心扩展法完成 [LC 5],理解后再实现 Manacher 版本对比性能。