516. 最长回文子序列

一般情况下,动态规划的解题步骤是:

  • 第一步:根据原问题和子问题来确定状态(我的dp数组要表示什么东西)
  • 第二步:根据状态确定状态转移方程(递推式,怎样求解dp数组)
  • 第三步:确定要不要优化和编程实现方式

(其实可以这样理解——第一步是确定“我是谁,我在哪”,第二步是确定“我从哪里来”或者“我到哪里去”,第三步是确定“我怎么去”。)

\(LPS\)问题

经典的区间\(DP问题\)

原问题是:给定字符串 \({s}\) 的最长回文子序列
子问题其实是把原问题进行分解,缩小范围,往往一个问题的子问题会有很多种表示方法,比如说: \({s}\)\({i}\) 个字符的最长回文子序列, \({s}\)\({i}\) 个字符的最长回文子序列等等等,这个时候要结合题目来看——我们知道回文串是个左右对称的结构,所以一个回文串的增加长度是通过左右两边各加一个相同字母来实现的,所以,我们倾向于把子问题描述成:“字符串 \({s}\) 的第 \({i}\) 个字符到第 \({j}\) 个字符的最长回文子序列长度”,那么我们就用dp数组来表示它—— \({dp[i][j]}\) 为字符串 \({s}\) 的第 \({i}\) 个字符到第 \({j}\) 个字符的最长回文子序列长度。

状态确定之后我们需要来考虑这个状态是怎么来的(怎么来和到哪里去考虑一个就好,过程是对称的)。考虑这个状态是如何得来的时候,只需要考虑最后一步是怎么走的,前面的步骤的包含着之前的状态里面了。

具体到这个题\(dp[i][j]\) 有两种情况:

  • \(s[i] == s[j]\):这个时候可以把\(s[i]\)\(s[j]\)分别加到原来0的回文子序列的两端,也就是 \(i+1\)\(j-1\) 这个区间的字符构成的最长回文子序列的两端,此时 \({dp[i][j] = dp[i+1][j-1] + 2 }\);
  • \(s[i] != s[j]\):那么在 \({i}\)\({j}\) 这个区间的最长回文子序列中第 \({i}\) 个字符和第 \({j}\) 个字符一定不能同时存在,所以是可以扔掉他俩中的某一个的(并不是任意一个),此时 \({dp[i][j] = max(dp[i+1][j],dp[i][j-1])}\)

其他的情况都是这两种情况的子情况了所以转移方程就出来啦!

状态表示:\(f(i,j)\):从下标\(i\)到下标\(j\)的最长回文子序列长度
状态转移:

\[f(i,j)=\begin{cases} f(i+1,j-1)+2,s[i]==s[j] &①\\ max(f(i+1,j),f(i,j-1)),s[i]≠s[j]&②\\ \end{cases} \]

①式很好理解,值得注意的是②式。

错误转移如下:
\(f(i,j)=f(i+1,j-1),s[i]≠s[j]\)

class Solution {
public:
    static const int N=1010;
    int f[N][N];

    int longestPalindromeSubseq(string s) {
        int n=s.size();

        for(int i=n-1;i>=0;i--)
            for(int j=i;j<n;j++)
                if(i == j) f[i][j]=1;
                else
                {
                    if(s[i] == s[j]) f[i][j]=max(f[i][j],f[i+1][j-1]+2);
                    else f[i][j]=max(f[i+1][j],f[i][j-1]);
                }
        return f[0][n-1];
    }
};
posted @ 2020-12-16 16:13  Dazzling!  阅读(166)  评论(0编辑  收藏  举报