Dynamic Programming学习笔记 (15) - 最长回文子序列 (力扣# 516)

166 阅读2分钟

回文序列是DP应用中经常见到的一类问题,这里的回文指的是一个字串,其字面从头到尾的排列顺序和从尾到头的排列顺序相同。 最长回文子序列问题的题面为:

给定一个长度为N的字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

实例如下:

s = "bbbab"
答案为4,最长回文子序列为 "bbbb" 。

解题思路:

我们可以定义一个表达式F(i, j),其中i是字符串的开始下标,j是结束下标,F(i, j)返回的是从i到j的子字符串中的最长回文序列的长度。

根据题面的要求,我们有以下两种情况

  1. s[i]与s[j]相同,在该情况下,如果i和j相同,那么F(i, j)返回的就是1,否则F(i, j)返回 2+ F(i + 1, j - 1)
  2. s[i]与s[j]不同,在该情况下,F(i, j)返回的是max(F(i + 1, j), F(i, j - 1))

边界情况是当i>j时,F(i, j)始终返回0。

从以上表达式出发,我们可以定义一个(N+1) * (N+1)的二维数组作为DP存储,然后使用双重循环,外层循环依据字串下标从大到小,内层循环依据字串下标从小到大,来依次计算DP数组中各个元素的值。最后DP[1][N]中的数值就是问题的最终答案。

Java代码如下:

class Solution {
    public int longestPalindromeSubseq(String s) {
        char[] chars = s.toCharArray();
        int N = chars.length;

        int[][] dp = new int[N + 1][N + 1];

        for (int i = N; i >= 1; i --) {
            for (int j = i; j <= N; j ++) {
                int len = 0;
                if (i == j) {
                    len = 1;
                } else {
                    if (chars[i - 1] == chars[j - 1]) {
                        len = 2 + dp[i + 1][j - 1];
                    } else {
                        len = Math.max(dp[i][j - 1], dp[i + 1][j]);
                    }
                }
                dp[i][j] = len;
            }
        }

        return dp[1][N];
    }
}