马拉车算法(最长回文子串)
以 babad 为例
**思路:**肯定是要遍历,记录以每个字符为中心的回文串长度。(时间复杂度O^2)但这样做有一个问题,无法解决偶数长度的字符串,如abba,就无法得到4
马拉车算法会将每个字符间插入#
babad–> #b#a#b#a#d#
计算最长回文子串过程如下:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
# | b | # | a | # | b | # | a | # | d | # | |
leni | 1 | 2 | 1 | 4 | 1 | 2 | 1 | 2 | 1 | 2 | 1 |
max r | 0 | 2 | 2 | 6 | 6 | 6 | 6 | 8 | 8 | 10 | 10 |
max i | 0 | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
leni以i为中心的回文子串半径长度
max r当前最长的回文串右边界下标
max i当前最长的回文串中心所在下标
i<maxr,其实也要暴力扩散,但是我们可以尽可能减少扩散的迭代数。我们取[maxr-i,leni[2maxi-i]]最小值作为扩散的起点。2maxi-i即为i关于maxi的对称点。
i>=maxr,暴力扩散
我们知道,最终得到的回文串肯定是以#开头,以#结束的回文串。这是因为只有当两边字符不相等时才会停止左右扩散,所以#是边界。也就是说子串中#的数量肯定是比剩下的字符多1的、
我们得到最大的leni(例子为4)时,此时回文串长度为2leni-1(例子为7),而其中有(2leni-1)/2+1个字符#(例子为4),所以剩下的字符即为我们要求的回文串长度(例子为3)
再以偶数长度字符串举例。baab
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
# | b | # | a | # | a | # | b | # | |
leni | 1 | 2 | 1 | 2 | 5* | 2 | 1 | 2 | 1 |
max r | 0 | 2 | 2 | 4 | 8 | 8 | 8 | 8 | 8 |
max i | 0 | 1 | 1 | 3 | 5 | 5 | 5 | 5 | 5 |
最大leni为5,此时回文串长度为9,其中有5个#,所以结果为4
回文串起始位置maxi-leni+1,结束位置maxi+leni-1
‘’,join(‘#’.split(s[maxi-leni+1:maxi+leni]))
class Solution(object):
# 暴力扩散 返回右边界
def expand(self,s, left, right):
while (left >= 0 and right < len(s) and s[left] == s[right]):
left -= 1
right += 1
return int((right - left) / 2)
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
appends = '#' + '#'.join(list(s)) + '#'
leni = list() # 以i为中心的回文子串半径长度
maxr = -1 # 当前最长的回文串右边界下标
maxi = -1 # 当前最长的回文串中心所在下标
maxlen = 0 # 记录最长回文串半径
leni_tmp=-1
for i in range(len(appends)):
if i >= maxr:
leni_tmp = self.expand(appends, i, i)
else:
min_tmp = min(maxr - i, leni[2 * maxi - i])
leni_tmp = self.expand(appends, i - min_tmp, i + min_tmp)
leni.append(leni_tmp) # 用于记录每一个回文子串长度
if leni_tmp > maxlen:# 更新当前最长子串
maxi = i
maxlen = leni_tmp
maxr = i + leni_tmp - 1
return ''.join(appends[maxi - maxlen + 1:maxlen + maxi].split('#'))
理解这个算法最好的办法就是把别人的代码自己跑一边,debug看看每一步的目的。