5. 最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 1:
输入:s = “cbbd”
输出:“bb”
解题思路
解法一:动态规划
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示字符串 s [ i : j + 1 ] s[i:j + 1] s[i:j+1] 是否为回文字符串,其状态转移公式为:若 s [ i ] = = s [ j ] s[i] == s[j] s[i]==s[j], d p [ i ] [ j ] = d p [ i + i ] [ j − 1 ] dp[i][j] = dp[i + i][j - 1] dp[i][j]=dp[i+i][j−1]; 否则, d p [ i ] [ j ] = F a l s e dp[i][j] = False dp[i][j]=False
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
if n < 2: return s
dp = [[False] * n for _ in range(n)]
# 单个字符均为回文字符串
for i in range(n):
dp[i][i] = True
start,maxL = 0,1
# 根据字符串长度遍历
for L in range(2,n + 1):
# 根据字符串左边界遍历:
for i in range(n):
j = i + L - 1
if j >= n or s[i] != s[j]: continue
# L= 2需要特殊考虑
dp[i][j] = True if L == 2 else dp[i + 1][j - 1]
if dp[i][j] and L > maxL: start,maxL = i,L
return s[start:start+maxL]
时间复杂度 = O ( N 2 ) O(N^2) O(N2),空间复杂度 = O ( N 2 ) O(N^2) O(N2)
解法二:中心扩展
本方法不同于上一种遍历左右边界,而是枚举可能存在的回文字符串的中心,尝试向左右两边堆成扩展。
如果两侧字符相同,则继续扩展,反之,得到当前中心下单最长回文字符串。
Trick: 对于偶数长度的回文字符串,他的回文中心是虚的。因此,为了方便我们做枚举,我们首先对原始字符串进行填充,即:在每个字符串间隔中添加一个新字符。
也就是说,字符串 abc
会被填充为 #a#b#c#
。那么得到的长度 // 2 即为原始的回文字符串长度。
注意添加的字符,即使在原始字符串中出现过,也不影响,因为他不可能和原始字符产生对应关系(上述例子中,#只会和#判断对应关系)
class Solution:
def longestPalindrome(self, s: str) -> str:
if len(s) < 2: return s
expanded = "#"
for c in s:
expanded += c + '#'
start,end,n = 0,0,len(expanded)
for i in range(len(expanded)):
for r in range(min(n - i,i + 1)):
if expanded[i - r] != expanded[i + r]:
r -= 1
break
if end - start < r * 2: start,end = i - r, i + r
return expanded[start + 1:end:2]
时间复杂度 = O ( N 2 ) O(N^2) O(N2),空间复杂度 = O ( N ) O(N) O(N)
解法三:Manacher算法
首先明确几个概念:
回文中心及右边界: 回文中心指回文串对对称中心,偶数长度的字符串回文中心是虚轴;右边界指回文字符串的结尾。
回文直径 / 半径: 直径集字符串长度,半径即从回文中心道右边界的长度。如:aba
的回文半径为
2
2
2,直径为
3
3
3;aa
的回文半径为
1
1
1,直径为
2
2
2。
- 和暴力解相似,我们先对原始字符串
s
进行扩充,而后遍历字符串。 - 当访问到位置
i
i
i,分为以下几种情况讨论:
- i i i >= R R R : 和暴力解一样向外扩,计算以字符最大长度
-
i
i
i <
R
R
R : 可以找到关于当前回文中心对称的
i
2
i2
i2 及当前回文子串的左边界
L
L
L
- 如果以 i 2 i2 i2为中心的回文子串在 L R L R LR 内部,则以 i i i为中心的回文子串直径与他相同
- 如果以 i 2 i2 i2为中心的回文子串在 L R L R LR 外部,则以 i i i为中心的回文子串半径是 R − i + 1 R - i + 1 R−i+1
- 如果以 i 2 i2 i2为中心的回文子串左边界为 L L L ,则以 i i i为中心的回文子串半径至少 R − i + 1 R - i + 1 R−i+1,基于此再尝试扩充
class Solution:
def expand(self,s,min_r,max_r,i):
for r in range(min_r,max_r):
if expanded[i - r] != expanded[i + r]:
r -= 1
break
return r
def longestPalindrome(self, s: str) -> str:
if len(s) < 2: return s
expanded = "#"
for c in s:
expanded += c + '#'
start,end,n = 0,0,len(expanded)
alens,right,center = [],-1,-1
for i in range(len(expanded)):
if i >= right:
r = self.expand(s,0,min(i + 1,n - i))
if end - start < r * 2: start,end = i - r, i + r
right,center = end,i
alens.append(r)
else:
left = center - alens[center]
i2 = 2 * center - i
i2left = i2 - alens[i2]
if i2left > left: alens.append(alens[i2])
elif i2left < left: alens.append(right - i + 1)
else:
r = self.expand(s,right - i + 1,min(n - i,i + 1),i)
if end - start < r * 2: start,end = i - r, i + r
right,center = end,i
alens.append(r)
return expanded[start + 1:end:2]
时间复杂度 = O ( N ) O(N) O(N),空间复杂度 = O ( N ) O(N) O(N)