Manacher Algorithm
Manacher Algorithm
Gleen K. Manacher (1975), "A new linear-time 'on-line' algorithm for finding the smallest initial palindrome of a string"
一种关于一个字符串 的回文子串的算法, 可以线性求两个数组 和 , 分别表示以第 位为中心的最长奇回文子串和最长偶回文子串的半径 (长度除以 , 向上取整). 对于以 为中心的回文子串 , 规定 .
根据回文串的性质可以知道 和 也恰好是以 为中心的回文子串的个数. 这条性质非常重要.
朴素
在求字符 的 时, 每次从 开始枚举, 只要 , 就让 增加 .
对于 同样如此, 只是边界换成 , 自增条件变成 .
容易看出, 这样最坏的复杂度可达 , 在 形如 aaaa...aaaa
时可以出现.
优化
还是先考虑 的求法.
由于朴素算法外层循环是从小到大按顺序的, 所以可以认为求 时, 已经求出来了.
设当前 最大的 (对应回文子串右边界最大) 的 为 , 则对 有两种情况:
这时的 不在任何已知的回文子串中, 则直接朴素求 .
这时的 在以 为中心的回文串内, 则必有 . 内涵是因为 在 的右边, 又在以 为中心的奇回文串内, 则在 左边的对应位置也应该有一个同样的字符. 设这个字符位置为 .
因为 在 左边, 所以 也一定求出来了, 所以 是回文串, 再加上 也是回文串, 所以 和 皆为回文串. 二者的中心都是 , 设二者的交集为 , 则 也是回文串.
这时 的中心是 .
这时, 可以断定 .
接下来, 考虑 是否能取更大值.
-
这时如果 , 则 . 字符 关于 的对称字符就是 , 因为 , 所以字符 也和前面提到的三个字符相同. 这样, . 但是 则说明 , 相矛盾, 所以 不能更大.
-
根据已经求出的 得, . 又因为 , 所以对称的 , 所以这种情况 也不会更大.
-
这时比较特殊, 因为 , . 所以就算是 也无所谓, 这不会和任何已经求出的 值冲突. 所以在 的基础上继续朴素即可.
对于求 的情况, 思路相同, 实现有细节上的差别, 不再赘述.
时间复杂度
以 为例.
在大部分情况下, 每个 的求出是 的, 只要考虑朴素时的复杂度即可.
-
当 时
一定有 , 被更新成 , 比原先多了至少 , 即本次操作步数.
-
当 , 但 时.
朴素从原来的 开始, 这时 , 接下来每执行一步, 都增加 .
因为每次朴素执行 步后, 都右移至少 位, 而 最大是 , 所以朴素的总复杂度是 .
综上, Manacher 可以在 , 求出长度为 的字符串的所有回文子串.
模板
一个长度为 的字符串, 求最长回文子串长度. ()
用 求出 , , 第 个字符为中心的最长回文子串即为 , 求 , 时顺便统计即可.
代码, 缺省源省略
unsigned n, Frontier(0), Ans(0), Tmp(0), f1[11000005], f2[11000005];
char a[11000005];
int main() {
fread(a+1,1,11000000,stdin); // fread 优化
n = strlen(a + 1); // 字符串长度
a[0] = 'A';
a[n + 1] = 'B'; // 哨兵
for (register unsigned i(1); i <= n; ++i) { // 先求 f1
if(i + 1 > Frontier + f1[Frontier]) { // 朴素
while (!(a[i - f1[i]] ^ a[i + f1[i]])) {
++f1[i];
}
Frontier = i; // 更新 Frontier
}
else {
register unsigned Reverse((Frontier << 1) - i), A(Reverse - f1[Reverse]), B(Frontier - f1[Frontier]);
f1[i] = Reverse - ((A < B) ? B : A); // 确定 f1[i] 下界
if (!(Reverse - f1[Reverse] ^ Frontier - f1[Frontier])) { // 特殊情况
while (!(a[i - f1[i]] ^ a[i + f1[i]])) {
++f1[i];
}
Frontier = i; // 更新 Frontier
}
}
Ans = ((Ans < f1[i]) ? f1[i] : Ans);
}
Ans = (Ans << 1) - 1; // 根据 max(f1) 求长度
Frontier = 0;
for (register unsigned i(1); i <= n; ++i) {
if(i + 1 > Frontier + f2[Frontier]) { // 朴素
while (!(a[i - f2[i] - 1] ^ a[i + f2[i]])) {
++f2[i];
}
Frontier = i; // 更新 Frontier
}
else {
register unsigned Reverse ((Frontier << 1) - i - 1), A(Reverse - f2[Reverse]), B(Frontier - f2[Frontier]);
f2[i] = Reverse - ((A < B) ? B : A); // 确定 f2[i] 下界
if (A == B) { // 特殊情况, 朴素
while (a[i - f2[i] - 1] == a[i + f2[i]]) {
++f2[i];
}
Frontier = i; // 更新 Frontier
}
}
Tmp = ((Tmp < f2[i]) ? f2[i] : Tmp);
}
Tmp <<= 1; // 根据 max(f2) 求长度
printf("%u\n", (Ans < Tmp) ? Tmp : Ans); // 奇偶取其大
return Wild_Donkey;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具