516. 最长回文子序列
给你一个字符串
s
,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。
提示:
1 <= s.length <= 1000
s
仅由小写英文字母组成
法1 动态规划 双序列型
思路
题目虽然给了一个字符串,但其实也可以看作双序列类型的题目。
题解
class Solution: def longestPalindromeSubseq(self, s: str) -> int: t = s[::-1] dp = [[0]*(len(t)+1) for _ in range(len(s)+1)] for i in range(1, len(dp)): for j in range(1, len(dp[0])): if s[i-1] == t[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) return dp[-1][-1]
法2 动态规划 区间型
思路
定义状态
dp[i][j]
表示子数组s[i:j+1]
之间的最长回文子序列。那么dp[i][j]
则: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])
整个动态规划的过程,即相当于在填充如下表格:

需要注意如下特殊情况。当两个字符挨着且相等的时候,也是从左下角 + 2,只不过此时左下角默认为0,也就不用特殊考虑了

初始化的时候,仅仅需要将对角线上的初始化为1。
然后判断的时候,并不需要专门取判断j = i+ 1 的特殊情况。因为j = i + 1 时,表明此时两个字符挨着,然后只有相等才会用到dp[i+1][j-1], 而此时dp[i+1][j-1] 默认就是0,所以不需要专门考虑,见上图
题解
class Solution: def longestPalindromeSubseq(self, s: str) -> int: if len(s) <= 1: return len(s) dp = [[0] * len(s) for _ in range(len(s))] for i in range(0, len(s)): dp[i][i] = 1 for i in range(len(s)-1, -1, -1): for j in range(i+1, len(s), 1): 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]) return dp[0][-1] # 可以将上述代码 优化,将初始化也融合到状态转移时刻进行初始化 class Solution: def longestPalindromeSubseq(self, s: str) -> int: if len(s) <= 1: return len(s) dp = [[0] * len(s) for _ in range(len(s))] for i in range(len(s)-1, -1, -1): # 初始化 dp[i][i] = 1 for j in range(i+1, len(s), 1): 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]) return dp[0][-1]