Manacher(最长回文子串)
💡 核心思想
Manacher 算法在 O(n) 时间内求出字符串中以每个字符为中心的最长回文半径。核心技巧是在字符间插入分隔符(如 #),将奇数长度和偶数长度的回文统一处理。利用对称性:如果当前点在已发现的最右回文串内部,则可以直接复制对称点的回文半径,只需少量扩展即可。
Manacher 是字符串竞赛的必备武器。看到"最长回文子串"就写中心扩展 O(n²)?Manacher 直接 O(n) 秒杀。核心就三个变量:当前最右回文边界 r,对应的中心 mid,以及每个位置的回文半径 p[i]。理解了对称性的利用,代码非常短。
🎯 直觉理解
想象你在沙漠里探测地下水。你已经挖了一口井(一个回文串),深度为 p[mid]。现在要探测新位置 i,如果 i 在你已挖的井的范围内,那么 i 的对称点 j = 2*mid - i 已经被探测过了,你可以直接参考 j 的深度,只需要再往下挖一点点验证。
📝 算法流程
- 在字符间插入 #,如 "aba" → "#a#b#a#"
- 初始化 p[i] = 0,mid = 0,r = 0
- 遍历每个位置 i:
- 如果 i < r,则 p[i] = min(p[2*mid - i], r - i)
- 中心扩展:while s[i+p[i]+1] == s[i-p[i]-1], p[i]++
- 如果 i + p[i] > r,更新 mid = i, r = i + p[i]
- 答案为 max(p[i]) - 1(因为插入了 #)
$$p[i] = \min(p[2 \cdot mid - i],\ r - i) \quad \text{(当 } i < r \text{ 时)}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(n)$(每个字符最多被比较一次) |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
int manacher(const string& s) {
int n = s.size();
string t = "#";
for (char c : s) { t += c; t += '#'; }
int m = t.size();
vector p(m, 0);
int mid = 0, r = 0, maxLen = 0;
for (int i = 0; i < m; i++) {
if (i < r) p[i] = min(p[2 * mid - i], r - i);
while (i - p[i] - 1 >= 0 && i + p[i] + 1 < m &&
t[i - p[i] - 1] == t[i + p[i] + 1]) p[i]++;
if (i + p[i] > r) { mid = i; r = i + p[i]; }
maxLen = max(maxLen, p[i]);
}
return maxLen; // 回文半径(含 #),实际长度为 maxLen
}
string longestPalindrome(const string& s) {
int n = s.size();
string t = "#";
for (char c : s) { t += c; t += '#'; }
int m = t.size();
vector p(m, 0);
int mid = 0, r = 0, center = 0;
for (int i = 0; i < m; i++) {
if (i < r) p[i] = min(p[2 * mid - i], r - i);
while (i - p[i] - 1 >= 0 && i + p[i] + 1 < m &&
t[i - p[i] - 1] == t[i + p[i] + 1]) p[i]++;
if (i + p[i] > r) { mid = i; r = i + p[i]; }
if (p[i] > p[center]) center = i;
}
int start = (center - p[center]) / 2;
return s.substr(start, p[center]);
}
int main() {
string s = "babad";
cout << manacher(s) << endl; // 3 ("bab" 或 "aba")
cout << longestPalindrome(s) << endl;
return 0;
} ⚠️ 常见坑点
分隔符选择——不要用字符串中已有的字符
p[i] 的含义——包含分隔符的回文半径,实际长度要减 1
数组越界——中心扩展时要检查边界
忘记更新 mid 和 r——找到更右的回文时要更新
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3805 最长回文子串 | 洛谷 | CSP-S | Manacher 模板题 |
| P1659 字符串变换 | 洛谷 | CSP-S | Manacher + 贪心 |
| P5446 字符串统计 | 洛谷 | CSP-S | Manacher + 计数 |