题目
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例1 :
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例2 :
输入: "cbbd"
输出: "bb"
想法一:以每个字符为中心向两边扫描
算法实现
def longestPalindrome(self, s: str) -> str:
if not s:
return ""
res = s[0]
n = len(s)
for i in range(1, n):
# 中轴情况
j = 1
while i - j > -1 and i + j < n and s[i - j] == s[i + j]:
j += 1
j -= 1
if len(res) < j * 2 + 1:
res = s[i - j:i + j + 1]
# 对称情况
j = 1
while i - j > -1 and i + j - 1 < n and s[i - j] == s[i + j - 1]:
j += 1
j -= 1
if len(res) < j * 2:
res = s[i - j:i + j]
return res
执行结果
执行结果 : 通过
执行用时 : 1176 ms, 在所有 Python3 提交中击败了68.60%的用户
内存消耗 : 13.6 MB, 在所有 Python3 提交中击败了33.03%的用户
复杂度分析
-
时间复杂度:O(n^2)
-
空间复杂度:O(1)
动态规划
算法实现
def longestPalindrome(self, s: str) -> str:
size = len(s)
if size < 2:
return s
dp = [[False for _ in range(size)] for _ in range(size)]
max_len = 1
start = 0
for i in range(size):
dp[i][i] = True
for j in range(1, size):
for i in range(0, j):
if s[i] == s[j]:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
else:
dp[i][j] = False
if dp[i][j]:
cur_len = j - i + 1
if cur_len > max_len:
max_len = cur_len
start = i
return s[start:start + max_len]
执行结果
复杂度分析
-
时间复杂度:O(n^2)
-
空间复杂度:O(n^2)
Manacher 算法
算法实现
def longestPalindrome(self, s: str) -> str:
# 特判
size = len(s)
if size < 2:
return s
# 得到预处理字符串
t = "#"
for i in range(size):
t += s[i]
t += "#"
# 新字符串的长度
t_len = 2 * size + 1
# 数组 p 记录了扫描过的回文子串的信息
p = [0 for _ in range(t_len)]
# 双指针,它们是一一对应的,须同时更新
max_right = 0
center = 0
# 当前遍历的中心最大扩散步数,其值等于原始字符串的最长回文子串的长度
max_len = 1
# 原始字符串的最长回文子串的起始位置,与 max_len 必须同时更新
start = 1
for i in range(t_len):
if i < max_right:
mirror = 2 * center - i
# 这一行代码是 Manacher 算法的关键所在,要结合图形来理解
p[i] = min(max_right - i, p[mirror])
# 下一次尝试扩散的左右起点,能扩散的步数直接加到 p[i] 中
left = i - (1 + p[i])
right = i + (1 + p[i])
# left >= 0 and right < t_len 保证不越界
# t[left] == t[right] 表示可以扩散 1 次
while left >= 0 and right < t_len and t[left] == t[right]:
p[i] += 1
left -= 1
right += 1
# 根据 max_right 的定义,它是遍历过的 i 的 i + p[i] 的最大者
# 如果 max_right 的值越大,进入上面 i < max_right 的判断的可能性就越大,这样就可以重复利用之前判断过的回文信息了
if i + p[i] > max_right:
# max_right 和 center 需要同时更新
max_right = i + p[i]
center = i
if p[i] > max_len:
# 记录最长回文子串的长度和相应它在原始字符串中的起点
max_len = p[i]
start = (i - max_len) // 2
return s[start: start + max_len]
执行结果
复杂度分析
-
时间复杂度:O(n)
-
空间复杂度:O(n^2)
小结
先按自己的想法设计,也是之前做过的题,想了很久决定每个字符向两边扫描会好一点,然后做了出来。之后看了动态规划和 Manacher 算法,基本上理解了。
Manacher 算法说白了就是利用中心扩散的信息来省去一些计算,非常厉害,然后题解里面讲的很详细,虽然看了很久才明白,但是看完以后还是觉得大致理解了,很不错。