力扣: 动态规划 最长回文子序列

675 阅读5分钟

动态规划

一说到动态规划啊,很多人都比较头疼,因为这种东西真是不把人当人,难起来真的连想破脑瓜子都写不出来,任是卡在某一个步骤,久久不能自拔。我也是饱受动态规划的摧残,被其折磨的遍体鳞伤,我都不想回忆那段不堪的时光。闲话不多说,我们先来认识一下它。

概念

什么叫做动态规划呢?动态规划的基本思想就是将待求解的问题分解成若干个子问题,利用这些子问题的解,得到原问题的解。通常动态规划的问题都伴随这动态数组dp来记录所有子问题的答案.计算一个子问题,就将其结果填入dp数组中,而dp数组也会随着问题的复杂程度而被定义为一维数组,二维数组,甚至三维数组,这是由问题决定的,有的时候一维数组可以解决,但是dp可能定义成二维数组会更加方便。而这dp数组的元素之间也常常具有某种关系,我们将其称为状态转移方程

动态规划法多用于来解决带有最优性质以及无后效性的问题,比如最长回文,以及我们熟知的背包问题等。那么这里需要解释一下什么叫做无后效性: 在某个阶段的状态一旦确定,则此后的过程不再受此前各状态及决策的影响,此阶段之后的变化仅与此阶段有关,而与此阶段之前的所经历的阶段无关。 也就是说,每一个子问题都是新的问题,在研究下一个子问题时,只与当前子问题有关,与该子问题之前的子问题无关

解题思想四部曲

1. 确定dp的状态 : 定义为一维二维还是三维?表示什么含义?

2. 确定状态转移方程

3. 初始化,也就是dp数组可以取得的元素的值

4. 自底向上的方式计算dp,并得出最优值

例题

力扣中的,最长回文子序列是这样描述的:给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列

基本思想

在做题之前我们先要分析,什么叫做回文子序列,这个可要和回文子串区分开来,回文子串必须是连续的,而回文子序列是在不改变字母顺序的情况下,组合而成的序列。比如 bab,回文子串ba,ab,bab,但是子序列多了一种子序列 bb,所以二者是不一样的。

那么接下来我们就按照四部曲来解释

dp数组的状态

首先,我们需要考虑如何定义dp数组,在此题中假设定义一维数组dp[],那么能表示什么含义呢?而且此处是子串,如何表示子串这个概念呢?所以很显然,这里定义一维数组并不合适。那我们将dp数组定义为二维数组,dp[i][j]的含义就表示从ij的子序列的最大长度。

状态方程

接下来,我们就需要思考状态方程了,这一直也是难点。我们需要抓住本质,在考录某个状态的时候,我们只需要知道dp[i][j]是如何定义状态的,并不需要具体了解是怎么推出来的。

一般在考虑的时候,并不讨论特殊情况,而是普遍情况

在回文中,最重要的逻辑就是s[i]=s[j],如果ij相邻,那么我们就可以定义ij的回文子序列中dp[i][j]的最大长度为2。所以我们可以继续延伸,如果字母更多会怎么样?那么此时,我们的下标就需要向外扩散,也就是说,当ij不相邻的时候,我们就需要判断在判断dp[i + 1][j - 1]中的子序列的最大长度为多少,那么这个时候dp[i][j] = dp[i + 1][j - 1] + 2.

由于是回文子序列,我们还需要考虑s[i]s[j]不相同的情况,那么这个时候说明s[i]s[j]的同时加入,并不能增加[i,j]区间回文子串的最大长度+2,那么此时就要单独加入s[i]、s[j],看看哪一个子序列可以组成最长的回文子序列。

如果加入s[j],那么原来的dp[i+1][j-1]的回文子序列就需要考虑dp[i + 1][j]的最大长度。

如果加入s[i],那么原来的dp[i+1][j-1]的回文子序列就需要考虑dp[i][j - 1]的最大长度。

那么dp[i][j]一定取二者的最大值,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

初始化

ij相同时,很显然,dp[i][j] = 1,也就是说,考虑ij相等时候的情况就将其置为1

自底向上

很显然,这里的计算顺序i需要从0开始,而j需要从s的最后一位开始。因为我们的dp[i][j]需要由dp[i][j - 1]得到,最后得到的最大值就为dp[0][size - 1]

具体代码

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;//初始化,将ij相等的情况都置为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];
    }
};

总结

动态规划这条路还是很长的,这也只是冰山一角,我也只是遨游在代码的海洋,近乎溺死,自己日后还是要加强练习,需要不断提升自己,不过遇到动态规划,四部曲还是很奏效的,至少不会这么无厘头,记住箴言:多练习,多学习,多复习,多思考!

希望可以帮到大家,谢谢!