Manacher
描述¶
给定一个长度为
更进一步的描述¶
显然在最坏情况下可能有
但是关于回文串的信息可用 一种更紧凑的方式 表达:对于每个位置
举例来说,字符串
字符串
因此关键思路是,如果以某个位置
一个令人惊讶的事实是,存在一个复杂度为线性并且足够简单的算法计算上述两个“回文性质数组”
解法¶
总的来说,该问题具有多种解法:应用字符串哈希,该问题可在
但是这里描述的算法 压倒性 的简单,并且在时间和空间复杂度上具有更小的常数。该算法由 Glenn K. Manacher 在 1975 年提出。
朴素算法¶
为了避免在之后的叙述中出现歧义,这里我们指出什么是“朴素算法”。
该算法通过下述方式工作:对每个中心位置
该算法是比较慢的:它只能在
该朴素算法的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // C++ Version
vector<int> d1(n), d2(n);
for (int i = 0; i < n; i++) {
d1[i] = 1;
while (0 <= i - d1[i] && i + d1[i] < n && s[i - d1[i]] == s[i + d1[i]]) {
d1[i]++;
}
d2[i] = 0;
while (0 <= i - d2[i] - 1 && i + d2[i] < n &&
s[i - d2[i] - 1] == s[i + d2[i]]) {
d2[i]++;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 | # Python Version
d1 = [0] * n
d2 = [0] * n
for i in range(0, n):
d1[i] = 1
while 0 <= i - d1[i] and i + d1[i] < n and s[i - d1[i]] == s[i + d1[i]]:
d1[i] += 1
d2[i] = 0
while 0 <= i - d2[i] - 1 and i + d2[i] < n and s[i - d2[i] - 1] == s[i + d2[i]]:
d2[i] += 1
|
Manacher 算法¶
这里我们将只描述算法中寻找所有奇数长度子回文串的情况,即只计算
为了快速计算,我们维护已找到的最靠右的子回文串的 边界
现在假设我们要对下一个
-
如果
i i > r 因此我们将连续地增加
d_1[i] [i - d_1[i] \dots i + d_1[i]] d_1[i] s d_1[i] (l, r) -
现在考虑
i \le r d_1[] (l, r) i j = l + (r - i) d_1[j] j i d_1[i] = d_1[j] j i \ldots\ \overbrace{ s_l\ \ldots\ \underbrace{ s_{j-d_1[j]+1}\ \ldots\ s_j\ \ldots\ s_{j+d_1[j]-1} }_\text{palindrome}\ \ldots\ \underbrace{ s_{i-d_1[j]+1}\ \ldots\ s_i\ \ldots\ s_{i+d_1[j]-1} }_\text{palindrome}\ \ldots\ s_r }^\text{palindrome}\ \ldots 然而有一个 棘手的情况 需要被正确处理:当“内部”的回文串到达“外部”回文串的边界时,即
j - d_1[j] + 1 \le l i + d_1[j] - 1 \ge r d_1[i] = d_1[j] i 实际上,为了正确处理这种情况,我们应该“截断”回文串的长度,即置
d_1[i] = r - i d_1[i] 该种情况的图示如下(以
j \ldots\ \overbrace{ \underbrace{ s_l\ \ldots\ s_j\ \ldots\ s_{j+(j-l)} }_\text{palindrome}\ \ldots\ \underbrace{ s_{i-(r-i)}\ \ldots\ s_i\ \ldots\ s_r }_\text{palindrome} }^\text{palindrome}\ \underbrace{ \ldots \ldots \ldots \ldots \ldots }_\text{try moving here} 该图示显示出,尽管以
j i i
最后,仍有必要提醒的是,我们应当记得在计算完每个
同时,再让我们重复一遍:计算偶数长度回文串数组
Manacher 算法的复杂度¶
因为在计算一个特定位置的答案时我们总会运行朴素算法,所以一眼看去该算法的时间复杂度为线性的事实并不显然。
然而更仔细的分析显示出该算法具有线性复杂度。此处我们需要指出,计算 Z 函数的算法 和该算法较为类似,并同样具有线性时间复杂度。
实际上,注意到朴素算法的每次迭代均会使
Manacher 算法的另一部分显然也是线性的,因此总复杂度为
Manacher 算法的实现¶
分类讨论¶
为了计算
1 2 3 4 5 6 7 8 9 10 11 12 13 | // C++ Version
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;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 | # Python Version
d1 = [0] * n
l, r = 0, -1
for i in range(0, n):
k = 1 if i > r else min(d1[l + r - i], r - i + 1)
while 0 <= i - k and i + k < n and s[i - k] == s[i + k]:
k += 1
d1[i] = k
k -= 1
if i + k > r:
l = i - k
r = i + k
|
计算
1 2 3 4 5 6 7 8 9 10 11 12 13 | // C++ Version
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 (0 <= i - k - 1 && 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;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 | # Python Version
d2 = [0] * n
l, r = 0, -1
for i in range(0, n):
k = 0 if i > r else min(d2[l + r - i + 1], r - i + 1)
while 0 <= i - k - 1 and i + k < n and s[i - k - 1] == s[i + k]:
k += 1
d2[i] = k
k -= 1
if i + k > r:
l = i - k - 1
r = i + k
|
统一处理¶
虽然在讲解过程及上述实现中我们将
给定一个长度为
对于字母间的
注意到,在对
上述结论建立了
由于该统一处理本质上即求
练习题目¶
本页面主要译自博文 Нахождение всех подпалиндромов 与其英文翻译版 Finding all sub-palindromes in
build本页面最近更新:,更新历史
edit发现错误?想一起完善? 在 GitHub 上编辑此页!
people本页面贡献者:OI-wiki
copyright本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用