动态规划----最长回文子序列
1 题目
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
2 分析
分析:
最值问题---->dp
判断是否回文串---->双指针两端判断是否相等
回文子序列---->也是从两端开始判断---->二维dp[i][j]
step1 定义dp
dp[i][j] 表示s[i,...,j] 字符串中的最长回文子序列的长度
step2 base case
dp[i][j] = 1 (i == j)
dp[i][j] = 0 (i > j)
step3 递推关系
s[i] == s[j]
dp[i][j] = dp[i + 1][j - 1] + 2
s[i] != s[j]
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1], dp(i + 1, j - 1))
step4 目标值 (dp函数中有s[i] == s[j], 所以为闭区间[])
dp[0][len(s) - 1] 表示整个字符串的最长回文子序列的长度
暴力递归代码:
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
def dp(i, j):
# base case
if i == j:
return 1
if i > j:
return 0
# 递推关系
if s[i] == s[j]:
return dp(i + 1, j - 1) + 2
else:
return max(dp(i + 1, j), dp(i, j - 1), dp(i + 1, j - 1))
return dp(0, len(s) - 1)
step5 重复子问题优化
从图中可以看出递归过程中存在重复子问题。优化方法:备忘录 or dp table.
备忘录添加:
a 在递归函数的开头,查询备忘录中是否已经有值,如果有直接返回备忘录中的值。
b 递归函数中所有更新递归值的地方,更新备忘录
c 在递归函数的最后返回备忘录中的值
dp table添加:
a 将base case中的值更新到dp table中
b 根据递推关系式,有方向的更新dp table
c 返回dp table中的目标值
递归 + 备忘录 代码:
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
def dp(i, j):
if i < j and memo[i][j] != 0:
return memo[i][j]
# base case
if i == j:
return 1
if i > j:
return 0
# 递推关系
if s[i] == s[j]:
memo[i][j] = dp(i + 1, j - 1) + 2
else:
memo[i][j] = max(dp(i + 1, j), dp(i, j - 1), dp(i + 1, j - 1))
return memo[i][j]
memo = [[0 for j in range(len(s))]for i in range(len(s))]
for i in range(len(s)):
memo[i][i] = 1
return dp(0, len(s) - 1)
dp table 代码:
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
dp = [[0 for j in range(len(s))] for i in range(len(s))]
for i in range(len(s)):
dp[i][i] = 1
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1] + 2
else:
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1], dp[i + 1][j - 1])
return dp[0][len(s) - 1]