「这是我参与2022首次更文挑战的第33天,活动详情查看:2022首次更文挑战」。
题目
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
思路
最长回文子序列是动态规划经典的题目,这题不太一样的地方在于,子序列不需要连续。
状态
不过可以采取类似的方式,首先定义状态:一个二维数组dp,dp[i][j]代表从下标i到下标j的子串中,最长回文子序列的长度。
初始值
- i == j,此时代表只有一个字符,肯定是回文,所以此时dp[i][j] = 1
- i > j,此时不存在子串,就更不用说回文了,所以此时dp[i][j] = 0
- i < j,此时没有初始值,需要dp
状态转移方程
- s[i] == s[j],那么这2个字符可以添加到s[i+1]~s[j-1]这个子串原有的最长回文子序列的头尾,得到
dp[i][j] = dp[i+1][j-1] + 2
我们可以看到上图的例子,已知dp[1][3] = 3,最长子序列为bcb,由于 s[0] == s[4],所以,dp[0][4] = dp[1][3] + 2 = 5
- s[i] <> s[j],那么s[i]和s[j]不可能同时是最长回文子序列的首尾,因为它们无法配对构成回文,所以最多只有一个参与到最长回文子序列,得到
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
还是用类似的例子,由于s[0] <> s[4],所以首尾的a、e不可能同时是最长回文子序列的首尾,所以dp[0][4]的最长回文子序列只可能是在s[0]~s[3]和s[1]~s[4]这2个字串的最长回文子序列中。
其他
根据状态转移方程,dp[i][j]相关的值为dp[i+1][j-1]、dp[i+1][j]、dp[i][j-1],所以,i的值需要从大到小遍历,j的值需要从小到大遍历,确保在求解dp[i][j]的时候,依赖的数值都已经有了。
Java版本代码
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len][len];
for (int i = len-1; i >= 0; i--) {
dp[i][i] = 1;
char start = s.charAt(i);
for (int j = i+1; j < len; j++) {
char end = s.charAt(j);
if (start == end) {
dp[i][j] = dp[i+1][j-1] + 2;
} else {
dp[i][j] = Integer.max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][len-1];
}
}