字符串的最长回文串:Manacher’s Algorithm
题目链接:Longest Palindromic Substring
1. 问题描述
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
2. 各种解法复杂度
- 暴力枚举:O(N^2)
- 记忆化搜索:O(N^2)
- 动态规划:O(N^2)
- Manacher’s Algorithm : O(N)
3. Manacher’s Algorithm
步骤:
-
字符串里相邻两个字符之间插入一个 # ,开头和结尾也加上 # 。
例如:
Str = “abaaba”, Trans = “#a#b#a#a#b#a#”.
注:Trans 为 transform -
声明一个数组 P[n] : 其中 n 为 Trans 的长度。P[index] 表示以 index 为中心的最长回文串长度(不包括 index 本身)。例如:
Trans = # a # b # a # a # b # a # P = 0 1 0 3 0 1 6 1 0 3 0 1 0
声明变量 Cur :表示当前回文串最长的中心的下标(current position),初值为0。
声明变量 Right : 表示以Cur为中心的回文串最右一个字符所处位置。 -
建立循环,以增量 index 为中心,扩展 index 。也就是检查 index 两边是否相等。如果相等, P[index] 加1。
在循环体中:
- 变量 index :
当前检查的元素的下标。 - 声明变量 index_ mirror :
以Cur为对称轴, index 的对称位置,index_mirror = Cur - ( index - Cur )
。 - 在扩展之前检查 Right 是否大于 index :
如果是,那么 P[index] 的值直接赋值为min(R - index, P[index_mirror])
;否则P[index] = 0。
- 变量 index :
-
如果 index + P[index] > Right ,将 Cur 更新为 index 。
-
找出 P[] 的最大值即是答案。
4. Q&A
-
为什么要插入 # ?
比如 abba ,以第一个 b 的下标为 index ,如果要比较 index 的回文,那么它就得比较 index 和 index+1
如果插入 #a#b#b#a# ,第一个 b 后面的 # 下标为 index ,则比较 index - 1 和 index + 1 这样代码更清晰,也更好理解。此时 # 的 P[] 值就表示该位置的回文串长度。 -
其他
如上图。由于我们已经检查出 Cur 位置的回文串长度是 9 ,那么 Cur 左右两边的 9 个字符是对称的。如果 index_mirror 的 P[] 值小于等于 R-index 的值(即距离),那么令 P[index] = P[index_mirror] ——对称的性质。为什么要小于他们的距离?因为如果大于他们的距离,例如图中最左边的a
,它回文串的范围超出了 Cur 回文串的范围,超出 Cur 范围的对称性是未知的。从图中我们可以看出,在对称右边的a
显然 P[] 值跟左边的不相等,它是 1 。此时只能以 index 为中心继续比较(而不是直接令 P[index] = P[index_mirror] ,因为对称性质无法使用)。在检测 index 的最大回文串时,如果检测到 index 的回文串长度最右侧大于 Cur 的最右侧,也就是 Right ,那么将 Cur 更新为 index 。因为如果后面的元素本来可以用更新前 Cur 的对称性,那么更新后的 Cur 的对称性它同样可以用。而 Cur 的更新会使得更后面的元素可以用其对称性。
参考链接: