【代码随想录】Day38~Day57动态规划

理论基础

  1. 确定dp数组(dp table)以及下标的含义

  1. 确定递推公式

  1. dp数组如何初始化

  1. 确定遍历顺序

  1. 举例推导dp数组

基础题目

斐波那契数量:力扣509

  1. 确定dp数组(dp table)以及下标的含义:第i个斐波那契数的数值为dp[i]

  1. 确定递推公式:dp[i]=dp[i-1]+dp[i-2]

  1. dp数组如何初始化:dp[0]=0,dp[1]=1(题目中给出)

  1. 确定遍历顺序:从前向后

  1. 举例推导dp数组

class Solution {
public:
    int fib(int n) {
        if(n<=1) return n;
        vector<int> dp(n+1);//0~n个数字
        dp[0]=0;dp[1]=1;//初始化
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];//递推公式
        }
        return dp[n];
    }
};

*别忘了边界条件的判断

爬楼梯:力扣70

  1. 确定dp数组(dp table)以及下标的含义:达到i阶楼梯有几种方法

  1. 确定递推公式:dp[i]=dp[i-1]+dp[i-2]

  1. dp数组如何初始化:dp[0]没有意义,dp[1]=1,dp[2]=2(题目中给出)

  1. 确定遍历顺序:由前向后

  1. 举例推导dp数组

class Solution {
public:
    int climbStairs(int n) {
        if(n<=1) return n;
        vector<int> dp(n+1);//0~n个数字
        dp[0]=0;dp[1]=1;dp[2]=2;//初始化
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];//递推公式
        }
        return dp[n];
    }
};

使用最小花费爬楼梯:力扣746

  1. 确定dp数组(dp table)以及下标的含义:达到下标i的位置所需要的花费为dp[i]

  1. 确定递推公式:dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

  1. dp数组如何初始化:(题目中需要向上跳跃才有体力值)dp[1]=0,dp[0]=0

  1. 确定遍历顺序:由前向后

  1. 举例推导dp数组

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+1);//0~n个数字
        dp[0]=0;dp[1]=0;//初始化
        for(int i=2;i<=cost.size();i++){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);//递推公式
        }
        return dp[cost.size()];
    }
};

*比上一题增加了花费

不同路径:力扣62

  1. 确定dp数组(dp table)以及下标的含义:从(0,0)走到(i,j)有几种不同的走法

  1. 确定递推公式:只允许向右和向下走 dp[i][j]=dp[i-1][j]+dp[i][j-1]

  1. dp数组如何初始化:第零行、第零列为1

  1. 确定遍历顺序:由左到右、由上到下

  1. 举例推导dp数组

class Solution {
public:
    int uniquePaths(int m, int n) { //m几行 n几列
        vector<vector<int>> dp(m, vector<int>(n, 0));
        //初始化 第零行第零列是1 表示有一种方法
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
        for(int j=0;j<n;j++){
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

不同路径2:力扣63

**同样的方法,只不过增加了障碍,就无法通过。因此,障碍后的路径都为0

  1. 确定dp数组(dp table)以及下标的含义:从(0,0)走到(i,j)有几种不同的走法

  1. 确定递推公式:只允许向右和向下走 dp[i][j]=dp[i-1][j]+dp[i][j-1] 如果有障碍就走不了;没有障碍进行递推

  1. dp数组如何初始化:第零行、第零列为1,如遇到障碍,障碍后均为0(表示不能通过)

  1. 确定遍历顺序:由左到右、由上到下

  1. 举例推导dp数组

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m=obstacleGrid.size();
        int n=obstacleGrid[0].size();
        if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1) return 0;
        vector<vector<int>> dp(m, vector<int>(n, 0));
        //注意特殊条件:如果在终点或起点出现障碍 白搭直接返回
        //初始化 第零行第零列是1 表示有一种方法
        for(int i=0;i<m&&obstacleGrid[i][0]==0;i++){
            dp[i][0]=1;
        }
        for(int j=0;j<n&&obstacleGrid[0][j]==0;j++){
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(obstacleGrid[i][j]==1) continue;//如果碰到障碍物--此路不通--走不了
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

整数拆分:力扣343

  1. 确定dp数组(dp table)以及下标的含义:对i进行拆分得到的最大乘积dp[i]

  1. 确定递推公式:dp[i]=j*dp[i-j]

  1. dp数组如何初始化:dp[0]=0,dp[1]=0没有意义,dp[2]=1

  1. 确定遍历顺序:由小到大

  1. 举例推导dp数组

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1);//从0~n个数 有n+1
        //初始化
        dp[0]=0;dp[1]=0;dp[2]=1;
        for(int i=3;i<=n;i++){//从3开始
            for(int j=1;j<=i/2;j++){
                dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));//j*(i-j)两个数 j*dp[i-j]三个以及以上的数
            }
        }
        return dp[n];
    }
};

*比较好理解,但是感觉比上面的题目难了不少

不同的二叉搜索树:力扣96

  1. 确定dp数组(dp table)以及下标的含义:有几种不同的二叉搜索树

  1. 确定递推公式:dp[i]+=dp[j-1]*dp[i-j] j为1~i的所有情况

  1. dp数组如何初始化:dp[0]=1(有一棵树)dp[1]=1(有一棵树)

  1. 确定遍历顺序:由小到大

  1. 举例推导dp数组

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1);//0~n一共n+1个数
        dp[0]=1;//初始化 分别只有一棵树
        for(int i=1;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i]+=dp[j-1]*dp[i-j]; //j-1左子树的个数 i-j是右子树的个数
            }
        }
        return dp[n];
    }
};

**对代码仍有不理解的地方,有时间再看一遍讲解

背包问题

01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

01背包理论基础

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

  1. 确定递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); (不放物品i和放物品i)

  1. dp数组如何初始化:背包容量j为0的话,即dp[i][0]=0;dp[0][j]即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值:①当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小;②j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品;其他下标初始为什么数值都可以,因为都会被覆盖

  1. 确定遍历顺序:先遍历 物品还是先遍历背包重量都可以

  1. 举例推导dp数组

#include<iostream>
#include<vector>
using namespace std;

void test01bagproblem(){
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    // 二维dp数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
    
    //初始化 行
    for(int j=0;j<=bagweight;j++){
        if(weight[0]>j) dp[0][j]=0;
        else dp[0][j]=value[0];
    }
    
    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

        }
    }
    
    cout << dp[weight.size() - 1][bagweight] << endl;
    
}

int main(){
    test01bagproblem();
}

01背包理论基础(滚动数组)

将二维数组变为一维数组

  1. 确定dp数组(dp table)以及下标的含义:dp[j] 容量为j的背包,所背的物品价值可以最大为dp[j]。

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); (不放物品i和放物品i)

  1. dp数组如何初始化:dp[0=0,因为背包容量为0所背的物品的最大价值就是0。其余初始化为0

  1. 确定遍历顺序:背包从大到小、先遍历物品后遍历背包顺序不可以变动

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}
  1. 举例推导dp数组

#include<iostream>
#include<vector>
using namespace std;

void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}

分割等和子集:力扣0416

  1. 确定dp数组(dp table)以及下标的含义:背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。当 dp[target] == target 的时候,背包就装满了。

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

  1. dp数组如何初始化:均初始化为0

  1. 确定遍历顺序

for(int i = 0; i < nums.size(); i++) {
    for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
        dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
    }
}
  1. 举例推导dp数组

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
        }
        if(sum%2==1) return false;
        int target=sum/2;
        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001,0);
        // 开始 01背包
        for(int i=0;i<nums.size();i++){
            for(int j=target;j>=nums[i];j--){
                dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if(dp[target]==target) return true;
        return false;
    }
};

最后一块石头的重量2:力扣1049

  1. 确定dp数组(dp table)以及下标的含义:背量为j的背包最大的重量为dp[j]

  1. 确定递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  1. dp数组如何初始化:均为0

  1. 确定遍历顺序

for (int i = 0; i < stones.size(); i++) { // 遍历物品
    for (int j = target; j >= stones[i]; j--) { // 遍历背包
        dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
    }
}
  1. 举例推导dp数组

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum=0;
        vector<int> dp(15000,0);//初始化
        for(int i=0;i<stones.size();i++){
            sum+=stones[i];
        }
        int target=sum/2;//会向下取整
        //开始动态规划
        for(int i=0;i<stones.size();i++){
            for(int j=target;j>=stones[i];j--){
                dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum-dp[target]-dp[target]; //sum-dp[target]是剩余的部分 差值是最后的重量
    }
};

目标和:力扣0494

left组合 - right组合 = target

left + right = sum

left - (sum - left) = target 推导出 left = (target + sum)/2

target固定,sum固定,left可以求出来

问题就是在集合nums中找出和为left的组合

  1. 确定dp数组(dp table)以及下标的含义:填满j(包括j)这么大容积的包,有dp[j]种方法

  1. 确定递推公式:

dp[j] += dp[j - nums[i]]
  1. dp数组如何初始化:dp[0]=1,其余均初始化为0

  1. 确定遍历顺序

 for(int i=0;i<nums.size();i++){
            for(int j=bagsize;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
  1. 举例推导dp数组

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
        }
        //没有解的两种情况
        if(abs(target)>sum) return 0;//S的绝对值已经大于sum
        if((target+sum)%2==1) return 0;//sum 是5,S是2的话其实就是无解
        int bagsize=(sum+target)/2;
        vector<int> dp(bagsize+1,0);//初始化为0
        dp[0]=1;//答案为0的时候有一种方法
        //动态规划求解的情况
        for(int i=0;i<nums.size();i++){
            for(int j=bagsize;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[bagsize];
    }
};

*有不明白的地方

一和零:力扣0474

m 和 n相当于是一个背包,两个维度的背包

  1. 确定dp数组(dp table)以及下标的含义:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。最终结果是dp[m][n]

  1. 确定递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

  1. dp数组如何初始化:初始化为0

  1. 确定遍历顺序:一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历

  1. 举例推导dp数组

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
        for(string str:strs){//遍历物品
            int x=0;int y=0;
            for(char c:str){
                if(c=='0') x++;
                else y++;
            }
            //遍历背包
            for(int i=m;i>=x;i--){
                for(int j=n;j>=y;j--){
                    dp[i][j]=max(dp[i][j], dp[i - x][j - y] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

*有不明白的地方

完全背包

完全背包又是也是01背包稍作变化而来,即:完全背包的物品数量是无限的。

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包理论基础

完全背包的物品是可以添加多次的,所以要从小到大去遍历

物品在外层循环,遍历背包容量在内层循环

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}
void test_CompletePack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    test_CompletePack();
}

零钱兑换2:力扣0518

钱币数量不限,就知道这是一个完全背包

组合数:组合不强调元素之间的顺序,排列强调元素之间的顺序

  1. 确定dp数组(dp table)以及下标的含义:装满容量为j的背包有dp[j]种方法

  1. 确定递推公式:dp[j] += dp[j - coins[i]](求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]]

  1. dp数组如何初始化:dp[0] = 1,其余初始化为0

  1. 确定遍历顺序

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}
  1. 举例推导dp数组

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1,0);//初始化
        dp[0]=1;
        for(int i=0;i<coins.size();i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]+=dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

组合数:外层for循环遍历物品,内层for遍历背包

排列数:外层for遍历背包,内层for循环遍历物品

组合总和4:力扣0377

排列数:排列强调顺序,(1,5)和(5,1)是两个不同的排列

  1. 确定dp数组(dp table)以及下标的含义:装满容量为j的背包有dp[j]种方法

  1. 确定递推公式:dp[j] += dp[j - coins[i]](求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]]

  1. dp数组如何初始化:dp[0] = 1,其余初始化为0

  1. 确定遍历顺序:先遍历背包再遍历物品

  1. 举例推导dp数组

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1,0);//初始化
        dp[0]=1;
        for(int j=0;j<=target;j++){
            for(int i=0;i<nums.size();i++){
                if(j-nums[i]>=0&&dp[j] < INT_MAX - dp[j - nums[i]]) //C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]
                    dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[target];
    }
};

爬楼梯:力扣0070

零钱兑换:力扣0322

  1. 确定dp数组(dp table)以及下标的含义:凑足总额为j所需钱币的最少个数为dp[j]

  1. 确定递推公式:dp[j] =min(dp[j - coins[i]] + 1, dp[j]);

  1. dp数组如何初始化:凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;因为要取得都是最小值,所以初始化为int的最大值

  1. 确定遍历顺序:不强调集合是组合还是排列

  1. 举例推导dp数组

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,INT_MAX);
        dp[0]=0;
        for(int i=0;i<coins.size();i++){//遍历背包
            for(int j=coins[i];j<=amount;j++){//遍历物品
                if(j-coins[i]>=0&&dp[j-coins[i]]!=INT_MAX)
                    dp[j]=min(dp[j],dp[j-coins[i]]+1);
            }
        }
        if(dp[amount]==INT_MAX) return -1;
        return dp[amount];
    }
};

完全平方数:力扣0279

  1. 确定dp数组(dp table)以及下标的含义:和为j的完全平方数的最少数量为dp[j]

  1. 确定递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);

  1. dp数组如何初始化:dp[0]=0,其余均为最大值这样再取最小值的时候才不会覆盖

  1. 确定遍历顺序:不强调集合是组合还是排列

  1. 举例推导dp数组

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0]=0;//初始化
        for(int i=0;i*i<=n;i++){//遍历物品
            for(int j=i*i;j<=n;j++){//遍历背包
                if(j-i*i>=0&&dp[j-i*i]!=INT_MAX)
                    dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
        if(dp[n]==INT_MAX) return -1;
        return dp[n];
    }
};

单词拆分:力扣0139

  1. 确定dp数组(dp table)以及下标的含义:字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词

  1. 确定递推公式:如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )

  1. dp数组如何初始化:dp[0]=true

  1. 确定遍历顺序:这里是求排列数,有一定的顺序要求,需要先遍历背包再遍历物品。

  1. 举例推导dp数组

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for(int i=1;i<=s.size();i++){
            //遍历背包
            for(int j=0;j<i;j++){
                //遍历物品
                string word=s.substr(j,i-j);
                if(wordSet.find(word)!=wordSet.end()&&dp[j]==true){
                    dp[i]=true;
                }
            }
        }
        return dp[s.size()];
    }
};

多重背包

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

多重背包理论基础

void test_multi_pack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    vector<int> nums = {2, 3, 2};
    int bagWeight = 10;
    for (int i = 0; i < nums.size(); i++) {
        while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开
            weight.push_back(weight[i]);
            value.push_back(value[i]);
            nums[i]--;
        }
    }

    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
        for (int j = 0; j <= bagWeight; j++) {
            cout << dp[j] << " ";
        }
        cout << endl;
    }
    cout << dp[bagWeight] << endl;

}
int main() {
    test_multi_pack();
}

总结

递推公式

问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

问装满背包有几种方法:dp[j] += dp[j - nums[i]]

问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

遍历顺序

01背包

二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。

一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历

完全背包

纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历

求组合数就是外层for循环遍历物品,内层for遍历背包

求排列数就是外层for遍历背包,内层for循环遍历物品

打家劫舍

打家劫舍:力扣198

  1. 确定dp数组(dp table)以及下标的含义:考虑下标i,偷的最大的金币数为dp[i],答案是dp[nums.size()-1]

  1. 确定递推公式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

  1. dp数组如何初始化:dp[0]=nums[0] dp[1]=max(nums[0],nums[1]),其余下标

  1. 确定遍历顺序:顺序遍历;for(i=2;i<nums.size();i++)

  1. 举例推导dp数组

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];
        vector<int> dp(nums.size());
        dp[0]=nums[0];
        dp[1]=max(nums[0],nums[1]);//取大的值
        for(int i=2;i<nums.size();i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[nums.size()-1];
    }
};

打家劫舍2:力扣213

区别:连城环了 首尾是挨着的

三种情况:①首尾都不选②选首元素③选尾元素==》取②③的最大值

  1. 确定dp数组(dp table)以及下标的含义:偷的最大的金币数为dp[i],答案是dp[nums.size()-1]

  1. 确定递推公式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

  1. dp数组如何初始化:dp[0]=nums[0] dp[1]=max(nums[0],nums[1]),其余下标

  1. 确定遍历顺序:顺序遍历;for(i=2;i<nums.size();i++)

  1. 举例推导dp数组

class Solution {
public:
    int rob1(vector<int>& nums,int start,int end) {
        vector<int> dp(nums.size());
        if(start==end) return nums[start];
        dp[start]=nums[start];
        dp[start+1]=max(nums[start],nums[start+1]);//取大的值
        for(int i=start+2;i<=end;i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[end];
    }
    int rob(vector<int>& nums) {
        //!!第一个房屋和最后一个房屋是紧挨着的
        //分为两种情况:①包含首元素 ②不包含首元素包含尾元素
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];
        int result1=rob1(nums,0,nums.size()-2);
        int result2=rob1(nums,1,nums.size()-1);
        return max(result1,result2);
    }
};

打家劫舍3:力扣337

区别:是二叉树形式

  1. 确定dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。

  1. 确定递推公式:偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; 不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

  1. dp数组如何初始化:确定终止条件在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

  1. 确定遍历顺序:后序遍历。 因为要通过递归函数的返回值来做下一步计算。通过递归左节点,得到左节点偷与不偷的金钱。通过递归右节点,得到右节点偷与不偷的金钱。

  1. 举例推导dp数组

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> robTree(TreeNode *cur){
        //终止条件
        if(cur==NULL) return vector<int> {0,0};
        //遍历顺序 做 右 中的后序遍历
        //递归左节点
        vector<int> leftdp=robTree(cur->left);
        //递归右结点
        vector<int> rightdp=robTree(cur->right);
        //单层递归逻辑
        //偷当前结点 左右孩子不偷
        int val1=cur->val+leftdp[0]+rightdp[0];
        //不偷当前结点 判断左右孩子大小 偷大的
        int val2=max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
        return vector<int> {val2,val1};
    }
    //主函数
    int rob(TreeNode* root) {
        vector<int> result=robTree(root);
        return max(result[0],result[1]);
    }
};

股票问题

买卖股票的最佳时机(只能买卖一次):力扣121

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持有股票的最大现金,dp[i][0]不持有股票的最大现金;结果:max(dp[size-1][0],dp[size-1][1]) **持有表示已经有,不一定是当天才买这个股票

  1. 递推公式:dp[i][0]=max(-dp[i-1][0] -price[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i])

  1. dp数组如何初始化:dp[0][0]=-price[0];dp[0][1]=0

  1. 确定遍历顺序:从前往后遍历---for(i=1;i<len;i++){}

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int a=prices[0];
        vector<vector<int>> dp(prices.size(), vector<int>(2));
        dp[0][0] -= prices[0];//持有股票
        dp[0][1]=0;//不持有股票
        for(int i=1;i<prices.size();i++){
            //从第二天开始才有利润
            dp[i][0]=max(dp[i-1][0],-prices[i]);//持有的
            dp[i][1]=max(dp[i-1][1],dp[i][0]+prices[i]);//不持有
        }
        return dp[prices.size()-1][1];
    }
};

买卖股票的最佳时机2(可以买卖多次):力扣122

一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0] 表示第i天持有股票所得现金。dp[i][1] 表示第i天不持有股票所得最多现金

  1. 递推公式:dp[i][0]=max(-dp[i-1][0],dp[i-1][1]-price[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i])

  1. dp数组如何初始化:

  1. 确定遍历顺序:

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len=prices.size();
        vector<vector<int>> dp(len,vector<int>(2,0));
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        for(int i=1;i<len;i++){
            dp[i][0]=max(dp[i-1][1]-prices[i],dp[i-1][0]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return dp[len-1][1];
    }
};

买卖股票的最佳时机3(最多买卖两次):力扣123

至多买卖两次,那么是买一次还是买两次呢??

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0] 不操作;dp[i][1]第一次持有;dp[i][2]第一次不持有;dp[i][3]第二次持有;dp[i][4]第二次不持有

  1. 递推公式:

dp[i][0]=dp[i-1][0]; 就相当于是0

dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i]);

dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]);

dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]);

dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]);

  1. dp数组如何初始化:dp[0][0]=0 dp[0][1]=-price[0] dp[0][2]=0 dp[0][3]=-price[0] dp[0][4]=0

  1. 确定遍历顺序:for(i=1;i<price.size();i++ ){} 最终dp[price.size()-1][4]

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0) return 0;
        vector<vector<int>> dp(prices.size(),vector<int>(5,0));
        dp[0][0]=0;//不持有
        dp[0][1]=-prices[0];//第一次持有
        dp[0][2]=0;//第一次不持有
        dp[0][3]=-prices[0];//第二次持有
        dp[0][4]=0;//第二次不持有
        for(int j=1;j<prices.size();j++){
            dp[j][0]=dp[j-1][0];
            dp[j][1]=max(dp[j-1][1],dp[j-1][0]-prices[j]);
            dp[j][2]=max(dp[j-1][2],dp[j-1][1]+prices[j]);
            dp[j][3]=max(dp[j-1][3],dp[j-1][2]-prices[j]);
            dp[j][4]=max(dp[j-1][4],dp[j-1][3]+prices[j]);
        }
        return dp[prices.size()-1][4];
    }
};

买卖股票的最佳时机4(最多买卖k次):力扣188

买卖股票k次,第一个维度是股票第二维度是次数2k

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] i:第几天 j:第几次买卖

  1. 递推公式:

dp[i][0]=dp[i-1][0]; 就相当于是0

dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i]); //第一次买

dp[i][2]=max(dp[i-1][2],dp[i-1][1]+price[i]);//第一次卖

dp[i][3]=max(dp[i-1][3],dp[i-1][2]-price[i]); //第二次买

dp[i][4]=max(dp[i-1][4],dp[i-1][3]+price[i]); //第二次卖

  1. dp数组如何初始化:for(j=1;j<2*k;j+=2){dp[0][j]=-price[0]}

  1. 确定遍历顺序:for(j=0;j<2*k;j+=2){dp[i][j+1]=max(dp[i-1][j+1],dp[i-1][j]-price[i]);dp[i][j+2]=max(dp[i-1][j+2],dp[i][j+1]+price[i])}

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(prices.size()==0) return 0;
        vector<vector<int>> dp(prices.size(),vector<int>(2*k+1,0));
        //初始化过程
        for(int j=1;j<2*k;j+=2){
            dp[0][j]=-prices[0];
        }
        //递推公式
        for(int j=0;j<2*k;j+=2){
            for(int i=1;i<prices.size();i++){
                dp[i][j+1]=max(dp[i-1][j+1],dp[i][j]-prices[i]);
                dp[i][j+2]=max(dp[i-1][j+2],dp[i][j+1]+prices[i]);
            }
        }
        return dp[prices.size()-1][2*k];
    }
};

最佳买卖股票时机含冷冻期(买卖多次,卖出有一天冷冻期):力扣309

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持股;dp[i][1]保持卖出股票的状态;dp[i][2] 真正卖出股票的状态;dp[i][3]冷冻期

  1. 递推公式:

dp[i][0]=max(dp[i-1][0]//持有 dp[i-1][3]-price[i],dp[i-1][1]//买入股票 前一天是冷冻期or前一天是保持卖出股票状态)

dp[i][1]=dp[i-1][1]//保持卖出股票状态dp[i-1][3]//冷冻期下一天

dp[i][2]=dp[i-1][1]+price[i]//前一天持有股票

dp[i][3]=dp[i-1][2]//前一天卖出股票,不是保持状态

  1. dp数组如何初始化:dp[0][0]=-price[0] dp[0][1]=0 dp[0][2]=0 dp[0][3]=0

  1. 确定遍历顺序:顺序遍历 i=1 ==》max(dp[pricesize()-1][1,2,3])

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0) return 0;
        int len=prices.size();
        vector<vector<int>> dp(len,vector<int>(4,0));
        dp[0][0]=-prices[0];//持股中
        for(int i=1;i<len;i++){
            dp[i][0]=max(dp[i-1][0],max(dp[i-1][3]-prices[i],dp[i-1][1]-prices[i]));
            dp[i][1]=max(dp[i-1][1],dp[i-1][3]);//保持要卖出股票的状态
            dp[i][2]=dp[i-1][0]+prices[i];
            dp[i][3]=dp[i-1][2];//前一天卖出股票,不是保持状态
        }
        return max(dp[len-1][1],max(dp[len-1][2],dp[len-1][3]));
    }
};

买卖股票的最佳实际含手续费(买卖多次,每次有手续费):力扣714

  1. 确定dp数组(dp table)以及下标的含义:dp[i][0]持有股最大现金;dp[i][1]不持有股票的最大先进

  1. 递推公式:

dp[i][0]=max(dp[i-1][0],dp[i-1][1]-price[i])

dp[i][1]=max(dp[i-1][1],dp[i-1][0]+price[i]-fee)

  1. dp数组如何初始化:dp[0][0]=-price[0] dp[0][1]=0

  1. 确定遍历顺序:顺序遍历 i=1 ==》max(dp[pricesize()-1][1,2,3])

  1. 举例推导dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n=prices.size();
        vector<vector<int>> dp(n,vector<int>(2,0));
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        for(int i=1;i<n;i++){
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]-fee);
        }
        return dp[n-1][1];
    }
};

子序列问题

子序列(不连续)

最长上升子序列:力扣300

  1. 确定dp数组(dp table)以及下标的含义:dp[i]以nums[i]为结尾的最长递增子序列的长度

  1. 确定递推公式:dp[i]=max(dp[i],dp[j]+1) (if(nums[i]<nums[j]))

  1. dp数组如何初始化:dp[i]=1;(因为至少有一个)

  1. 确定遍历顺序:i从小到大遍历;内层循环j从小到大遍历

  1. 举例推导dp数组

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //定义dp数组,初始化为1,因为至少会有长度为1的解
        vector<int> dp(nums.size(),1); 
        //递推公式,如果后面的比前面的大,就让长度+1
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        //循环,找到数组中的最大值 最大的长度
        int result=0;
        for(int i=0;i<nums.size();i++){
            result=max(result,dp[i]);
        }
        return result;
    }
};

最长公共子序列:力扣1143

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以0~i-1为结尾的nums1,以0~j-1为结尾的nums2的最长公共子序列的长度

  1. 确定递推公式:dp[i]=dp[i-1][j-1]+1 (if(nums1[i-1]<nums2[j-1]))else dp[i][j]=max( dp[i][j-1],dp[i-1][j])

  1. dp数组如何初始化:dp[i][0]和dp[0][j]=0,其余均初始为0

  1. 确定遍历顺序:从1开始,哪个先遍历无所谓,从小到大遍历,小于等于nums1.size()

  1. 举例推导dp数组

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        //定义二位dp数组,初始化为0
        vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));
        int result=0;//存储最终结果
        //循环打印dp数组
        for(int i=1;i<=text1.size();i++){
            for(int j=1;j<=text2.size();j++){
                if(text1[i-1]==text2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
                result=max(result,dp[i][j]);
            }
        }
        return result;
    }
};

不相交的线:力扣1035

如果找到最长公共子序列的话,他们的线就不会相交,因为保证了相同的顺序

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以0~i-1为结尾的nums1,以0~j-1为结尾的nums2的最长公共子序列的长度

  1. 确定递推公式:dp[i]=dp[i-1][j-1]+1 (if(nums1[i-1]<nums2[j-1]))else dp[i][j]=max( dp[i][j-1],dp[i-1][j])

  1. dp数组如何初始化:dp[i][0]和dp[0][j]=0,其余均初始为0

  1. 确定遍历顺序:从1开始,哪个先遍历无所谓,从小到大遍历,小于等于nums1.size()

  1. 举例推导dp数组

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        int result=0;
        for(int i=1;i<=nums1.size();i++){
            for(int j=1;j<=nums2.size();j++){
                if(nums1[i-1]==nums2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
                }
                result=max(result,dp[i][j]);
            }
        }
        return result;
    }
};

子序列(连续)

最长连续递增子序列:力扣674

  1. 确定dp数组(dp table)以及下标的含义:dp[i]以nums[i]为结尾的最长递增子序列的长度

  1. 确定递推公式:dp[i]=max(dp[i],dp[i]+1) (if(nums[i-1]<nums[i]))

  1. dp数组如何初始化:dp[i]=1;(因为至少有一个)

  1. 确定遍历顺序:i从小到大遍历

  1. 举例推导dp数组

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        //初始化dp数组均为1
        vector<int> dp(nums.size(),1);
        //地推公式
        for(int i=1;i<nums.size();i++){
            if(nums[i-1]<nums[i]){
                dp[i]=dp[i-1]+1;
            }
        }
        //找最大长度
        int result=0;
        for(int i=0;i<nums.size();i++){
            result=max(result,dp[i]);
        }
        return result;
    }
};

最长重复子数组:力扣718

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以i-1为结尾的nums1,以j-1为结尾的nums2的最长公共子序列的长度

  1. 确定递推公式:dp[i]=dp[i-1][j-1]+1 (if(nums1[i-1]<nums2[j-1]))

dp数组如何初始化:dp[i][0]和dp[0][j]=0,其余均初始为0

  1. 确定遍历顺序:从1开始,哪个先遍历无所谓,从小到大遍历,小于等于nums1.size()

  1. 举例推导dp数组

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        //定义二位dp数组,初始化为0
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        int result=0;//存储最终结果
        //循环打印dp数组
        for(int i=1;i<=nums1.size();i++){
            for(int j=1;j<=nums2.size();j++){
                if(nums1[i-1]==nums2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                result=max(result,dp[i][j]);
            }
        }
        return result;
    }
};

最大子序和:力扣53

  1. 确定dp数组(dp table)以及下标的含义:dp[i]以i为结尾(nums[i])的最长子序列和

  1. 确定递推公式:dp[i]=max(dp[i-1]+nums[i],nums[i])

  1. dp数组如何初始化:初始为0

  1. 确定遍历顺序:从1开始,从小到大遍历

  1. 举例推导dp数组

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //前提条件
        if(nums.size()==0) return 0;
        if(nums.size()==1) return nums[0];
        //初始化数组为0
        vector<int> dp(nums.size(),0);
        dp[0]=nums[0];
        //递归dp数组
        for(int i=1;i<nums.size();i++){
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
        }
        //循环dp数组找到最大值
        int result=INT_MIN;
        for(int i=0;i<nums.size();i++){
            result=max(result,dp[i]);
        }
        return result;
    }
};

编辑距离

判断子序列:力扣392

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以i-1为结尾的字符串s和以j-1为结尾的字符串t的相同子序列的长度和

  1. 确定递推公式:if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j-1]+1 else dp[i][j]=dp[i][j-1]

  1. dp数组如何初始化:dp[i][0]=0 dp[0][j]=0 均初始化为0即可

  1. 确定遍历顺序:从左到右从上到下 for(i=1;i<=s.size;i++){for(j=1;j<=t.size;j++){ }}

结果dp[s.size][t.size]==s.size 相等

  1. 举例推导dp数组

class Solution {
public:
    bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
        for(int i=1;i<=s.size();i++){
            for(int j=1;j<=t.size();j++){
                if(s[i-1]==t[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=dp[i][j-1];
                }
            }
        }
        if(dp[s.size()][t.size()]==s.size()) return true;
        else return false;
    }
};

不同的子序列:力扣115

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以i-1为结尾的字符串s中有以j-1为结尾的t的个数为dp[i][j]

  1. 确定递推公式:if(s[i-1]==t[j-1]) dp[i][j]=dp[i-1][j-1]+dp[i-1][j] else dp[i][j]=dp[i-1][j]

  1. dp数组如何初始化:dp[i][0]=1 dp[0][j]=0 dp[0][0]=1 均初始化为0即可

  1. 确定遍历顺序:从左到右从上到下 for(i=1;i<=s.size;i++){for(j=1;j<=t.size;j++){ }}

结果dp[s.size][t.size]

  1. 举例推导dp数组

class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1));
        dp[0][0]=1;
        for(int i=1;i<s.size();i++){
            dp[i][0]=1;
        }
        for(int j=1;j<t.size();j++){
            dp[0][j]=0;
        }
        for(int i=1;i<=s.size();i++){
            for(int j=1;j<=t.size();j++){
                if(s[i-1]==t[j-1]){
                    dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                }
                else{
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[s.size()][t.size()];
    }
};

两个字符串的删除操作:力扣583

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以i-1为结尾的字符串word1和以j-1为结尾的字符串word2为相同的最小操作次数为dp[i][j]

  1. 确定递推公式:if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1] else dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+2)

  1. dp数组如何初始化:dp[0][j]=j dp[i][0]=i 均初始化为0即可

  1. 确定遍历顺序:从左到右从上到下 for(i=1;i<=s.size;i++){for(j=1;j<=t.size;j++){ }}

结果dp[word1.size()][word2.size()]

  1. 举例推导dp数组

class Solution {
public:
    int minDistance(string word1, string word2) {
        //确定dp数组
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
        //初始化
        for(int i=0;i<=word1.size();i++){
            dp[i][0]=i;
        }
        for(int j=0;j<=word2.size();j++){
            dp[0][j]=j;
        }
        //递推公式 遍历顺序
         for (int i = 1; i <= word1.size(); i++) {
            for (int j = 1; j <= word2.size(); j++) {
                if (word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                }
            }
        }
        //得出结果
        return dp[word1.size()][word2.size()];
    }
};

编辑距离:力扣72

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j]以i-1为结尾的字符串word1和以j-1为结尾的字符串word2为相同的最少操作次数为dp[i][j]

  1. 确定递推公式:if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1] else(不相同的情况有增、删、替换)删除 增加 修改:dp[i][j]=min(dp[i-1][j]+1dp[i][j-1]+1,dp[i-1][j-1]+1)

  1. dp数组如何初始化:dp[0][j]=j dp[i][0]=i 均初始化为0即可

  1. 确定遍历顺序:从左到右从上到下 for(i=1;i<=s.size;i++){for(j=1;j<=t.size;j++){ }}

结果dp[word1.size][word2.size]

  1. 举例推导dp数组

class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
        //初始化
        for(int i=0;i<=word1.size();i++){
            dp[i][0]=i;
        }
        for(int j=0;j<=word2.size();j++){
            dp[0][j]=j;
        }
        //确定遍历顺序
        for(int i=1;i<=word1.size();i++){
            for(int j=1;j<=word2.size();j++){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j]=dp[i-1][j-1];
                }
                else{
                    //对字母 删除 增加 修改
                    dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

编辑距离总结篇

回文

回文子串:力扣647

单独一个元素一定是回文子串 回文串左右两边的元素是相同对称的

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] [i,j]子串是否是回文子串 true/false

  1. 确定递推公式:if(s[i]==s[j]){if(j-i<=1){dp[i][j]=true;result++;}else if(dp[i+1][j-1]==true){dp[i][j]=true;result++}}dp[i][j]=false i==j a;j-i=1 aa;j-i>1一定是大于2的子串 判断i+1和j-1之间是不是回文子串即dp[i+1][j-1]==true

  1. dp数组如何初始化:false

  1. 确定遍历顺序:从左到右从下到上 for(i=s.szie-1;i>=0i--){for(j=i;j<=s.size;j++){递推公式 }}

  1. 举例推导dp数组

class Solution {
public:

    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
        int result=0;
        for(int i=s.size()-1;i>=0;i--){
            for(int j=i;j<s.size();j++){
                if(s[i]==s[j]){
                    if(j-i<=1){
                        dp[i][j]=true;
                        result++;
                    }
                    else if(dp[i+1][j-1]==true){
                        dp[i][j]=true;
                        result++;
                    }
                }
            }
        }
        return result;
    }
};

最长回文子序列:力扣516

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

  1. 确定dp数组(dp table)以及下标的含义:dp[i][j] [i,j]内的回文子序列长度为dp[i][j]

  1. 确定递推公式:if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1]+2(相同) else dp[i][j]=max(dp[i][j-1],dp[i+1][j])(不相同)

  1. dp数组如何初始化:dp[i][i]=1(i,j相同的情况下初始化为1) 其余下标默认初始化为0

  1. 确定遍历顺序:从左到右从下到上 for(i=s.szie-1;i>=0i--){for(j=i+1;j<=s.size;j++){递推公式 }}

  1. 举例推导dp数组

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;
        }
        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];
    }
};

历时将近一个月,中间经历了大大小小的事,终于完成了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值