C++怎么使用Manacher算法_C++回文串教程【高效】

1次阅读

manacher算法需预处理为”^#a#b#a#$”格式,rad[i]表示含中心的回文半径,左边界为i−rad[i]+1;数组大小至少2n+5,更新时取min(rad[j], r−i+1);还原原串位置用start=(i−rad[i]+1)/2、len=rad[i]−1。

C++怎么使用Manacher算法_C++回文串教程【高效】

Manacher算法在c++里怎么写才不越界

Manacher算法核心是预处理字符串、维护回文半径数组 rad[] 和中心位置 mid,但新手常卡在边界计算上——比如用 i - rad[i] 当左边界时没考虑 rad[i] 是“包含中心的长度”,实际左边界该是 i - rad[i] + 1;或者预处理插入的分隔符(如 '#')导致原串下标映射出错。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 预处理统一用 "^#a#b#a#$" 形式:开头加 '^'、结尾加 '$',避免边界判断;中间每个字符前后都插 '#',这样所有回文长度必为奇数,rad[i] 直接表示以 i 为中心的回文半径(含中心)
  • rad 数组大小至少为 2 * n + 5n 是原串长),别只开 n+1
  • 更新 rad[i] 时,若 i (<code>r = mid + rad[mid] - 1),先取对称点 j = 2 * mid - i,再取 rad[i] = min(rad[j], r - i + 1) —— 注意是 r - i + 1,不是 r - i

为什么不能直接用String::substr暴力找最长回文

因为时间复杂度是 O(n³):两层循环枚举端点,再一层验证是否回文。而 Manacher 是 O(n),关键在利用已算过的回文信息跳过重复比较。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 当输入长度 n > 1e5 时,暴力法必然超时;Manacher 在 n = 1e6 下仍稳定运行
  • 如果只要判断是否存在长度 ≥ k 的回文,不用存所有 rad[i],边算边比 rad[i] >= k 即可,省空间
  • 注意:Manacher 找的是“最长回文子串”,不是“最长回文子序列”——后者要用 DP,别混

C++里char数组和string混用容易崩在哪

常见错误是把 std::string 直接传给期望 const char* 的函数(比如 strlen()),或用 string.c_str() 后又修改了原 string,导致指针悬空。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • Manacher 预处理推荐用 std::string 构造新串,比如:string t = "^"; for (char c : s) { t += '#'; t += c; } t += "#$";
  • 若坚持用 char[],务必手动补 '',且分配长度 ≥ 2 * s.size() + 5,否则 strlen() 或越界读
  • 别在循环里反复调用 s.Length()——虽然现代编译器会优化,但明确写成变量更稳:int len = t.length();

如何从rad[]还原原始串里的最长回文位置

rad[i] 对应的是预处理串中以 i 为中心、长度为 rad[i] 的回文。要转回原串下标,得反向映射:预处理串中位置 i 对应原串字符位置是 (i - 1) / 2(因为 ^#a#b#a#$ 中,a 出现在索引 2、4、6…)

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 找到最大 rad[i] 后,计算原串起始位置:start = (i - rad[i] + 1) / 2,长度:len = rad[i] - 1(因为每个原字符被 '#' 包围,真实长度 = 半径 – 1)
  • 如果原串含中文或 UTF-8 多字节字符,Manacher 会失效——它按字节操作,不是按 Unicode 字符。此时必须先转为宽字符或用其他算法
  • 多个相同最大长度时,rad[i] 第一次出现最大值的位置对应最左解,无需额外排序

最易被忽略的是预处理串的边界字符 '^''$':它们不只是占位符,更是让 while 循环不必每轮都判边界的关键——少写一个,rad[i] 就可能越界访问。

text=ZqhQzanResources