题目链接:
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
提示:
-
1 <= s.length <= 1000
-
s
仅由小写英文字母组成
解题思路
注意本题与
求回文子串和回文子序列都是动态规划的经典题目。
-
dp数组的含义
dp[i][j]
表示字符串s
在[i,j]
范围内最长的回文子序列的长度。 -
递推公式
关键看
s[i]
和s[j]
是否相同-
s[i] == s[j]
,那dp[i][j]
就等于字符串s
在[i+1,j-1]
范围内最长的回文子序列的长度加上字符s[i]
和s[j]
。即:dp[i][j]=dp[i+1][j-1]+2
-
s[i] != s[j]
,那就考虑在s[i+1,j-1]
的基础上加入左边的字符s[i]
或右边的字符s[j]
所能得到的最长长度。-
加入左边的字符
s[i]
,dp[i][j]=dp[i][j-1]
-
加入右边的字符
s[j]
,dp[i][j]=dp[i+1][j]
取两者的最大值:
dp[i][j]=max(dp[i][j-1],dp[i+1][j])
-
-
-
dp的初始化
首先考虑
i = j
,从递推公式:dp[i][j]=dp[i+1][j-1]+2
可以看出:递推公式是计算不到i
和j
相同时的情况。所以需要手动初始化一下:当i=j
,那么dp[i][j]
一定是等于1的,即:一个字符的回文子序列长度就是1。其他情况
dp[i][j]
初始为0就行,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
中dp[i][j]
才不会被初始值覆盖。 -
遍历顺序
从递推公式来看,遍历
i
的时一定要从下到上遍历,遍历j
时一定要从左到右遍历。
C++
class Solution { public: int longestPalindromeSubseq(string s) { vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0)); for (int i = 0; i < s.size(); i++) { dp[i][i] = 1; } for (int i = s.size() - 1; i >= 0; i--) { for (int j = i + 1; j < s.size(); 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]); } } } return dp[0][s.size() - 1]; } };
/** * @param {string} s * @return {number} */ var longestPalindromeSubseq = function(s) { const dp = new Array(s.length).fill().map(item => Array(s.length).fill(0)); for (let i = 0; i < s.length; i++) { dp[i][i] = 1; } for (let i = s.length - 1; i >= 0; i--) { for (let j = i + 1; j < s.length; j++) { if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); } } } return dp[0][s.length - 1]; };