最长回文子串
题目描述:
给你一个字符串 s,找到 s 中最长的回文子串。
示例 :
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
提示:
- 1 <= s.length <= 1000
- s 仅由数字和英文字母(大写和/或小写)组成
解法一:中心扩展
- 回文串有两种样式,第一种是长度为奇数的情况,会有一个中心点,例如aba;第二种是长度为偶数的情况,会有两个中心点,例如abba。
- 基于此,一个直观的思路就是遍历,以每个字符为中心点,或者紧挨着的两个字符为中心点,然后向两边不断扩展,直到不满足回文的要求。
代码
class Solution:
def longestPalindrome(self, s: str) -> str:
res, max_len = s[0], 1
for idx in range(len(s) - 1):
substr1, len1 = Solution.func(s, idx, idx) # 以当前字符为中心点
substr2, len2 = Solution.func(s, idx, idx + 1) # 以当前字符和下一个字符为中心点
# 更新最优解
[res, max_len] = [substr1, len1] if len1 > max_len else [res, max_len]
[res, max_len] = [substr2, len2] if len2 > max_len else [res, max_len]
return res
def func(s: str, left: int, right: int) -> str:
while left >= 0 and right < len(s) and s[left] == s[right]: # 不越界并且两端字符相同
right += 1
left -= 1
return s[left + 1: right], right - left - 1 # 返回子串和长度
测试结果
执行用时:1080 ms, 在所有 Python3 提交中击败了63.98% 的用户
内存消耗:14.8 MB, 在所有 Python3 提交中击败了95.04% 的用户
其他方式
思路其实是直观的,有一个可探讨的点是,怎么更新当前最优解。上述代码的思路就是把子串和长度都返回,然后如果有更长的子串,就更新最优子串。其实也有很多其它的方式,举一个更为简单的例子,就是整个过程只记录最优子串的两个端点,最后再截取即可。代码:
class Solution:
def longestPalindrome(self, s: str) -> str:
left, right = 0, 0
for idx in range(len(s) - 1):
left1, right1 = Solution.func(s, idx, idx)
left2, right2 = Solution.func(s, idx, idx + 1)
if right1 - left1 > right - left:
left, right = left1, right1
if right2 - left2 > right - left:
left, right = left2, right2
return s[left: right + 1]
def func(s: str, left: int, right: int) -> str:
while left >= 0 and right < len(s) and s[left] == s[right]:
right += 1
left -= 1
return left + 1, right - 1
# 执行用时:924 ms, 在所有 Python3 提交中击败了75.49% 的用户
# 内存消耗:15.1 MB, 在所有 Python3 提交中击败了50.91% 的用户
解法二:动态规划
- 从解法一也可以看出,回文串可以看作不断向两边扩展的过程,这样的话就可以使用动态规划的方法。但其实我个人觉得解法一的思路更为直观一点。
- 我们使用二维布尔数组 dp[][] 来记录回文串的情况:
dp[i][j] = True if s[i: j+1] is a palindromic-substring else False
- 我们也可以推出更新公式:
True, if i == j
dp[i][j] = s[i] == s[j], if i == j-1
dp[i+1][j-1] and s[i] == s[j], otherwise
- 其实比较关键的是怎么进行更新,因为大的回文串是建立在小的回文串的基础上,所以我们应该先更新所有长度为1的回文串,进而更新长度为 2,3,··· 的回文串。
代码
class Solution:
def longestPalindrome(self, s: str) -> str:
n, start, end = len(s), 0, 0
dp = [[False] * n for _ in range(n)]
for k in range(n): # k 是间隔,间隔为 k,子串长度为 k+1
for i in range(n - k):
j = i + k
if k == 0:
dp[i][j] = True
elif k == 1:
dp[i][j] = s[i] == s[j]
else:
dp[i][j] = dp[i+1][j-1] and s[i] == s[j]
if dp[i][j]: # 因为 k 越来越大,字串的长度也是不断增大的。
start, end = i, j
return s[start: end + 1]
测试结果
执行用时:9172 ms, 在所有 Python3 提交中击败了13.87% 的用户
内存消耗:22.5 MB, 在所有 Python3 提交中击败了33.19% 的用户
说明
算法题来源:力扣(LeetCode)