leetcode-最长回文子序列

172 阅读1分钟

「这是我参与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

1.png 我们可以看到上图的例子,已知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])

2.png 还是用类似的例子,由于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];
    }
}