Manacher算法用回文半径数组p避免重复扩展:当i在已知回文右边界R内,利用对称点i_mirror的p[i_mirror]提供下界min(p[i_mirror], R−i);i≥R时才暴力扩展;预处理需加'#'及首尾哨兵以统一处理奇偶回文;最大回文原串起始位置为(max_i−p[max_i]+1)/2,长度为p[max_i]−1。

Manacher算法核心思想:用回文半径数组 p 避免重复扩展
Manacher不是靠暴力扩中心,而是利用已计算的回文信息“复用”——当当前中心 i 落在某个已知回文右边界 R 内时,它关于当前最右回文中心 C 的对称点 i_mirror = 2*C - i 的回文半径 p[i_mirror] 可以直接提供下界。这个下界是 min(p[i_mirror], R - i),因为超出 R 的部分还没验证过。
关键点在于:只有当 i 超出当前 R(即 i >= R),才必须从头暴力扩展;否则先设 p[i] = min(...),再尝试向两边继续扩。每次成功扩展都更新 C 和 R。
注意:p[i] 表示以位置 i 为中心的**回文半径长度**(含中心),即该回文在预处理串中的总长为 2 * p[i] - 1。
预处理字符串必须加分隔符,否则偶数长回文无法对齐
原始字符串如 "abba" 没有单字符中心可覆盖整个串。Manacher统一处理奇偶的方式是插入唯一分隔符(如 '#'),首尾再加不同哨兵(如 '^' 和 '$')避免越界检查:
abba → ^#a#b#b#a#$
这样每个原字符和每对相邻字符之间都有明确中心,所有回文都变成奇数长度。原串中长度为 L 的回文,在新串中对应半径 p[i] = L + 1(因为每个字符被 # 包裹,新增 L+1 个 #,总长 2L+1,半径就是 L+1)。
常见错误:只加 '#' 不加首尾哨兵,导致循环里要反复判断边界;或用空格代替 '#',结果原串含空格时逻辑错乱。
manacher() 函数返回的是原串坐标,不是预处理串坐标
算法跑完 p[] 数组后,要反推原串中的起始位置。假设在预处理串中最大半径出现在索引 max_i,对应半径 p[max_i],那么:
- 该回文在预处理串中覆盖范围是
[max_i - p[max_i] + 1, max_i + p[max_i] - 1] - 去掉所有
'#'和哨兵后,原串起始下标是(max_i - p[max_i] + 1) / 2(整除,因奇数位才是原字符) - 原串长度就是
p[max_i] - 1(因为半径多算了中心那个'#'或哨兵带来的偏移)
别直接用 max_i 当原串下标——它可能指向 '#',也可能落在 '^' 上。必须通过整数除法映射回去。
边界和初始化细节决定是否漏解或越界
初始化时 C = R = 0 是安全的,但要注意:R 是**已知最右回文的右边界索引+1**(即开区间 [0, R) 内的信息可信),所以判断用 i ,不是 <code>i 。常见崩溃点:
- 没加哨兵,
while扩展时未检查i - p[i] >= 0 && i + p[i] ,导致数组越界 -
p数组开小了:预处理串长为2 * s.length() + 3,p至少要开这么大 - 遍历找最大
p[i]时,从1开始(跳过'^'),到n-2结束(跳过'$')
实际写的时候,宁可多算两步,也别省掉边界判断——Manacher的性能优势本就建立在“大部分位置靠查表而非扩展”上,安全比省几行代码重要得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











