给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路1:【中心扩散法】记得以前做过很多回文的变形题,现在貌似又的差不多了。言归正传,这题可以向在每个字符间插入一个自定义符号例如#等,这样可以保证一定最大的回文字符串一定是奇数的,也就是一定以某个字符为中心,可以将(aba和aa)这两种一起处理了。然后现在我能想到的只有暴力了,遍历每一个字符,以它为中心,往前往后分别遍历,找到最长回文。
复杂度应该是:o(n^2)
class Solution:
def longestPalindrome(self, s: str) -> str:
s_2 = '#'
for i in range(len(s)):
s_2 += s[i] + '#'
max_s = 0
max_len = 0
for i in range(1, len(s_2)):
k = 1
while i - k >= 0 and i + k < len(s_2):
if s_2[i-k] == s_2[i+k]:
k += 1
else:
break
k -= 1
if max_len < k * 2 + 1:
max_s = i - k # 回文的起点
max_len = k * 2 + 1
# print(max_s, k, max_len)
# print(s_2)
# print(max_s, max_len)
# print(s_2[max_s:(max_s+max_len)].replace('#', ''))
return s_2[max_s:(max_s+max_len)].replace('#', '')
思路2:动态规划
对于字符串str,假设dp[i,j]=1表示str[i...j]是回文子串,那个必定存在dp[i+1,j-1]=1。这样最长回文子串就能分解成一系列子问题,可以利用动态规划求解了。
首先构造状态转移方程
上面的状态转移方程表示,当str[i]=str[j]时,如果str[i+1...j-1]是回文串,则str[i...j]也是回文串;如果str[i+1...j-1]不是回文串,则str[i...j]不是回文串。
初始状态
-
dp[i][i]=1
-
dp[i][i+1]=1 if str[i]==str[i+1]
上式的意义是单个字符,两个相同字符都是回文串。
注意递推时,按照长度一次为2,3,4...的顺序进行推算。
class Solution:
def longestPalindrome(self, s: str) -> str:
length = len(s)
dp = [[0]*length for _ in range(length)]
for i in range(length):
dp[i][i] = 1
max_i = 0
max_j = 0
max_len = 0
# 控制每次的区间长度-1
for k in range(1, length):
# i为区间起始坐标,j为区间结束坐标
for i in range(0, length - k):
j = i + k
if s[i] == s[j]:
# print(i, j)
if i == j - 1: # 长度为2,K=1时需要特判
dp[i][j] = 1
else:
dp[i][j] = dp[i+1][j-1]
if dp[i][j] == 1:
if k + 1 > max_len:
max_i = i
max_j = j
max_len = k + 1
else:
dp[i][j] = 0
return s[max_i:max_j+1]
思路3:Manacher's Algorithm(马拉车算法)
参考文档:https://blog.csdn.net/weixin_43272781/article/details/89713050
参考视频:UESTCACM 每周算法讲堂 manacher算法
本人总结下关键点:
①定义的几个变量:
p[]: p[i]表示以i为中心的回文字符串的半径;
id : 表示目前延伸最远的回文字符串的半径;(并不是最大,只是最右的一个回文串)
mx: 表示以id为中心的回文串的右边界外第一个点位置;
②递推关系:
设i,j是与id为中心的对称点,那么在已知p[j]的情况下,需要计算p[i]就有以下两种情况:
1、p[j]在id的回文串内部,那么p[i]就也在内部,可以计算得到 p[i] = p[j] = p[2*id-i],因为i + j = 2 * id(id为中心)。
2、p[j]超过了id的回文串范围,那么p[i]最小也等于 mx - i。
总结为: p[i] = min(p[2*id-i], mx-i)
注意:此时得到目前的p[i]后依然要进行扩展以i为中心的回文串,因为此时获得的是最小半径。
复杂度:O(n)
class Solution:
def longestPalindrome(self, s: str) -> str:
s_2 = '#'
for i in range(len(s)):
s_2 += s[i] + '#'
# print(s_2)
length = len(s_2)
# p[i]以i中心的回文半径
p = [1] * length
id = 0 # 某个延伸最远的回文字符串的中心(该回文串不一定最长)
mx = 1 # 某个延伸最远的回文字符串能达到的最右端的值+1(即在回文外的第一个值)
max_s = 0
max_r = 0
for i in range(length):
if i < mx:
p[i] = min(p[2*id-i], mx-i)
while i - p[i] >= 0 and i + p[i] < length and s_2[i - p[i]] == s_2[i + p[i]]: # 拓展i为中心的回文串
p[i] += 1
if i + p[i] > mx: # 如果以i为中心的回文串延伸的更远,则进行更新
mx = i + p[i]
id = i
if p[i] > max_r: # 跟新最大回文字符串
max_s = i
max_r = p[i]
# print(max_s, max_r)
return s_2[max_s-max_r+1:max_s+max_r-1].replace('#', '')