给定一个长度为 n 的字符串 s,请找到所有对 (i,j) 使得子串 s[i…j] 为一个回文串。当 t=trev 时,字符串 t 是一个回文串(trev 是 t 的反转字符串)。
解释
显然在最坏情况下可能有 O(n2) 个回文串,因此似乎一眼看过去该问题并没有线性算法。
但是关于回文串的信息可用 一种更紧凑的方式 表达:对于每个位置 i=0…n−1,我们找出值 d1[i] 和 d2[i]。二者分别表示以位置 i 为中心的长度为奇数和长度为偶数的回文串个数。换个角度,二者也表示了以位置 i 为中心的最长回文串的半径长度(半径长度 d1[i],d2[i] 均为从位置 i 到回文串最右端位置包含的字符个数)。
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]++; }}
Python
d1 = [0] * nd2 = [0] * nfor 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
然而更仔细的分析显示出该算法具有线性复杂度。此处我们需要指出,计算 Z 函数的算法 和该算法较为类似,并同样具有线性时间复杂度。
实际上,注意到朴素算法的每次迭代均会使 r 增加 1,以及 r 在算法运行过程中从不减小。这两个观察告诉我们朴素算法总共会进行 O(n) 次迭代。
Manacher 算法的另一部分显然也是线性的,因此总复杂度为 O(n)。
Manacher 算法的实现
分类讨论
为了计算 d1[],我们有以下代码:
[list2tab]
C++
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; }}
Python
d1 = [0] * nl, r = 0, -1for 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
计算 d2[] 的代码十分类似,但是在算术表达式上有些许不同:
[list2tab]
C++
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; }}
Python
d2 = [0] * nl, r = 0, -1for 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