适合小学六年级的同学理解马拉车算法(Manacher’s Algorithm),我们会用有趣的故事和简单代码来解释。
一、故事理解:用镜子找宝藏 🔍
假设我们要在字符串中找到最长的回文(正反读都一样的字符串),比如在字符串 S = "abba"
中找最长回文:
-
插入分隔符:把字符串变成
T = "#a#b#b#a#"
- 作用:统一奇偶长度的回文查找
- 就像在字符之间放镜子,方便反射观察
-
维护三个法宝:
C
:当前回文的中心(像灯塔)R
:已知回文的最右边界(像地图边界)P[i]
:每个位置的回文半径(记录每个点的能量)
-
镜面反射技巧:
- 当探测新位置
i
时,用C
的镜像位置mirror = 2*C - i
直接复制半径 - 像用镜子快速复制已知信息,避免重复计算
- 当探测新位置
二、C++代码实现 🖥️
#include <iostream>
#include <vector>
using namespace std;
string longestPalindrome(string s) {
if (s.empty()) return "";
// 1. 插入分隔符(变成奇数长度)
string T = "#";
for (char c : s) {
T += c;
T += '#';
}
int n = T.size();
vector<int> P(n, 0); // 每个中心的回文半径
int C = 0, R = 0; // 当前中心和右边界
int maxLen = 0, center = 0;
for (int i = 0; i < n; i++) {
// 2. 找镜像位置,快速获得初始半径
int mirror = 2 * C - i;
if (i < R) {
P[i] = min(R - i, P[mirror]);
}
// 3. 中心扩展
int left = i - (P[i] + 1);
int right = i + (P[i] + 1);
while (left >= 0 && right < n && T[left] == T[right]) {
P[i]++;
left--;
right++;
}
// 4. 更新中心和边界
if (i + P[i] > R) {
C = i;
R = i + P[i];
}
// 5. 记录最大值
if (P[i] > maxLen) {
maxLen = P[i];
center = i;
}
}
// 转换回原字符串位置
int start = (center - maxLen) / 2;
return s.substr(start, maxLen);
}
int main() {
string s = "abba";
cout << "最长回文子串:" << longestPalindrome(s) << endl;
return 0;
}
三、关键步骤图解 🎨
以输入 "abba"
为例:
步骤 | 操作 | T字符串 | P数组变化 |
---|---|---|---|
1 | 插入分隔符 | #a#b#b#a# | 初始化全0 |
2 | i=1时中心扩展 | 找到半径1 | P[1]=1 |
3 | i=4时发现最长回文 | 半径4(实际长度4) | P[4]=4 |
4 | 转换回原字符串 | abba | 最终结果 |
四、复杂度分析 ⚡
- 时间复杂度:O(n) → 比暴力法O(n²)快得多
- 空间复杂度:O(n) → 存储每个位置的半径
关键技巧:通过镜像反射避免重复计算,像用镜子复制已知信息!