定义
回文串:正向读和反向读完全相同的字符串,例如 “aba”、“abba”。
Manacher 算法 由 Glenn K. Manacher 于 1975 年提出,能在 时间内求出字符串中所有回文子串的信息。
核心概念
对于字符串 s 的每个位置 i,我们计算:
- :以 i 为中心的奇数长度回文串个数(即半径长度)
- :以 i 为中心的偶数长度回文串个数
的含义是以 i 为中心、最长回文串的半径(包含字符个数)。类似地, 表示以 i 和 i-1 为中心的偶数长度回文串半径。
算法思想
维护已找到的最靠右的回文串边界 (即具有最大 r 值的回文串)。
状态转移
对于位置 i:
- i > r:使用朴素算法继续扩展
- i ≤ r:利用对称性,令 ,则 至少等于
边界情况
当以 j 为中心的回文串超出外部回文串边界时,不能直接复制,需要截断:
然后再用朴素算法尝试继续扩展。
代码实现
计算奇数长度回文串
vector<int> d1(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 1 : min(d1[l + r - i], r - i + 1);
while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
k++;
}
d1[i] = k--;
if (i + k > r) {
l = i - k;
r = i + k;
}
}计算偶数长度回文串
vector<int> d2(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 0 : min(d2[l + r - i + 1], r - i + 1);
while (i - k - 1 >= 0 && i + k < n && s[i - k - 1] == s[i + k]) {
k++;
}
d2[i] = k--;
if (i + k > r) {
l = i - k - 1;
r = i + k;
}
}统一版本
为简化代码,可以先将字符串处理成奇数长度形式:
// 在每两个字符之间插入 '#'
string t = "#";
for (char c : s) {
t += c;
t += '#';
}这样只需要计算 数组,原字符串位置 i 的回文长度就是 。
复杂度分析
-
时间复杂度:
- 朴素算法每次迭代都会使 r 增加 1
- r 在算法运行过程中从不减小
- 因此朴素算法总共只进行 次迭代
-
空间复杂度:
完整示例
#include <bits/stdc++.h>
using namespace std;
// Manacher 算法求最长回文子串
pair<int, int> longest_palindrome(const string& s) {
int n = s.size();
vector<int> d1(n), d2(n);
// 奇数长度
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 1 : min(d1[l + r - i], r - i + 1);
while (i - k >= 0 && i + k < n && s[i - k] == s[i + k]) k++;
d1[i] = k--;
if (i + k > r) {
l = i - k;
r = i + k;
}
}
// 偶数长度
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 0 : min(d2[l + r - i + 1], r - i + 1);
while (i - k - 1 >= 0 && i + k < n && s[i - k - 1] == s[i + k]) k++;
d2[i] = k--;
if (i + k > r) {
l = i - k - 1;
r = i + k;
}
}
// 找最长回文
int max_len = 1, pos = 0;
for (int i = 0; i < n; i++) {
if (d1[i] * 2 - 1 > max_len) {
max_len = d1[i] * 2 - 1;
pos = i;
}
if (d2[i] * 2 > max_len) {
max_len = d2[i] * 2;
pos = i;
}
}
int start = pos - (max_len - 1) / 2;
return {start, max_len};
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin >> s;
auto [start, len] = longest_palindrome(s);
cout << s.substr(start, len) << endl;
cout << "Length: " << len << endl;
return 0;
}应用场景
- 最长回文子串:直接应用
- 回文子串计数:
- 不同回文串个数:可用后缀数组或哈希进一步处理
- 构造回文:通过已知回文结构进行字符串构造
与其他算法的对比
| 算法 | 时间复杂度 | 适用场景 |
|---|---|---|
| 朴素算法 | 小规模数据 | |
| 字符串哈希 + 二分 | 需要多次查询 | |
| Manacher | 求所有回文信息 |