使用马拉车算法解决最长回文子串问题
使用马拉车算法(即 Manacher's Algorithm)可以将解决最长回文子串(即 Longest Palindromic Substring)问题的时间复杂度降至 O(n)。
参考:Manacher's Algorithm 马拉车算法 - 刷尽天下
1 最长回文子串问题
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad" |
Example 2:
Input: "cbbd" |
2 马拉车算法
2.1 添加字符 # 用以解决回文子串中的奇偶性问题
添加字符 #,使得字符串 s 中每个字符的左右两侧都有一个 #,比如:
bob -> #b#o#b# |
目的在于,不管原回文子串的长度是奇是偶,处理后的新回文子串的长度永远为奇数,便于处理。
2.2 定义数组 radius 用以表示各元素的最长回文子串半径
定义数组 radius,其长度与处理后的新字符串 ns 的长度保持一致,**radius 中每个位置的值表示以 ns 对应位置的字符为中心的最长回文子串的半径,**维护结束的 radius 大概是这个样子:
ns: # a # b # b # a # b # b # |
目的在于,**radius 中某个位置的值减一之后刚好等于原字符串 s 中回文子串的长度,**举个例子:下图中的 radius[4] == 5,也就是说以 ns[4] == '#' 为中心的最长回文子串 #a#b#b#a# 的半径为5,5 - 1 == 4,对应原字符串中的最长回文子串为 abba,其长度刚好是 4,符合规律且数学上也很容易证明。

只知道长度无法确定子串,还需要知道子串的起始位置。为了便于计算,我们在新字符串 ns 开头加入字符 $,此时 radius 中某个位置的下标减去其值,再除以 2 刚好等于原字符串 s 中回文子串的起始位置,举个例子:下图中的 radius[8] == 6,也就是说以 ns[8] == 'a' 为中心的最长回文子串 #b#b#a#b#b# 的半径为 6,(8 - 6) / 2 == 1,对应原字符串中的最长回文子串为 bbabb,其起始位置刚好是 1,符合规律且数学上也很容易证明。

2.3 核心:如何维护数组 radius
定义四个变量,分别为:
idx和rad:在所有已遍历过的位置中,必然存在某个最长回文子串能够向右到达最远位置,那么这个子串的中心就是idx,半径就是rad。res_idx和res_rad:在所有已遍历过的位置中,必然存在某个位置上的最长回文子串的长度最长,那么这个子串的中心就是res_idx,半径就是res_rad。
结合下面这句代码来理解具体的维护过程:
for (int i = 1; i < ns.size(); ++i) |
对于在新字符串 ns 中遍历到的下标 i,我们想知道以 i 为中心的最长回文子串的半径,从半径为 1 开始遍历自然就不能体现这个算法的精妙了,为此我们考虑以下两种情况:
如果
idx + rad > i,说明i没有超出idx和rad代表的最长回文子串的范围,那么根据回文性质可以知道,在这个子串中,下标i的对称位置为下标idx-(i-idx)(即2*idx-i),而radius中下标2*idx-i的位置必然已经维护过了,也就是说以2*idx-1为中心的最长回文子串的半径已经知道了,那么以i为中心的最长回文子串的半径可以从radius[2*idx-1]开始遍历。![]()
但是,对称位置
2*idx-1的最长回文子串的半径可能超出了idx和rad代表的最长回文子串的范围,其超出部分不可能出现在i的右侧,但未超出部分还是可以保证满足回文要求的,那么以i为中心的最长回文子串的半径必须从idx+rad-i开始遍历。![]()
所以,以
i为中心的最长回文子串的半径从min(radius[2*idx-i], idx+rad-i)开始遍历。如果
idx + rad <= i,说明i超出idx和rad代表的最长回文子串的范围,那么不存在对称位置可以参考,所以只能从半径为1开始遍历。
然后,维护那四个变量:
如果以
i为中心的最长回文子串能够向右到达更远位置,则更新idx和rad。如果以
i为中心的最长回文子串的长度更长,则更新res_idx和res_rad。
最后,遍历结束,根据前文提到的两个规律从原字符串 s 中截取子串即可。
3 代码实现
class Solution { |
4 时间和内存对比
使用常规算法,时间和内存分别为
16 ms和8.6 MB。使用马拉车算法,时间和内存分别为
4 ms和9.7 MB。

