Leetcode_9【动态规划】//待完善

目录

动态规划理论基础

509. 斐波那契数 

70. 爬楼梯   

746. 使用最小花费爬楼梯 

 62.不同路径 

 63. 不同路径 II

 343. 整数拆分 

 96.不同的二叉搜索树 

背包问题

01背包问题 二维 

01背包问题 一维(滚动数组)

416.分割等和子集  

1049.最后一块石头的重量 II 

494.目标和 

474.一和零  

 完全背包理论基础

 518.零钱兑换 II  

 377.组合总和 Ⅳ  

70. 爬楼梯 (进阶) 

322. 零钱兑换  

279.完全平方数  

139.单词拆分 

多重背包

背包问题总结篇 

打家劫舍

198.打家劫舍 ——线性

213.打家劫舍II ——环形

337.打家劫舍III  ——二叉树

买卖股票的最佳时机

121. 买卖股票的最佳时机 

122.买卖股票的最佳时机II  

123.买卖股票的最佳时机III  

188.买卖股票的最佳时机IV  

309.最佳买卖股票时机含冷冻期 

714.买卖股票的最佳时机含手续费  

子序列

300.最长递增子序列 

674. 最长连续递增序列 

718. 最长重复子数组 

1143.最长公共子序列 

1035.不相交的线 

53. 最大子序和 

编辑距离

392.判断子序列 

115.不同的子序列 

583. 两个字符串的删除操作 

72. 编辑距离 

编辑距离总结篇 

647. 回文子串   

516.最长回文子序列 

动态规划总结篇 


动态规划理论基础

代码随想录

视频:从此再也不怕动态规划了,动态规划解题方法论大曝光 !| 理论基础 |力扣刷题总结| 动态规划入门_哔哩哔哩_bilibili

动态规划的五个步骤:
  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

dubeg

  1. 找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
  2. 做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果
  3. 然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。
  4. 如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。
  5. 如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。

509. 斐波那契数 

很简单的动规入门题,但简单题使用来掌握方法论的,还是要有动规五部曲来分析。

代码随想录

视频:手把手带你入门动态规划 | LeetCode:509.斐波那契数_哔哩哔哩_bilibili

class Solution {
public:
    int fib(int n) {
        if(n <= 1) return n;
        vector<int> fib(n+1);
        fib[0] = 0;
        fib[1] = 1;
        for(int i = 2; i <=n; i++){
            fib[i] = fib[i-1] + fib[i-2];
        }
        return fib[n];
    }
};

70. 爬楼梯   

本题大家先自己想一想, 之后会发现,和 斐波那契数 有点关系。

代码随想录

视频:带你学透动态规划-爬楼梯(对应力扣70.爬楼梯)| 动态规划经典入门题目_哔哩哔哩_bilibili

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n+1);
        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. 使用最小花费爬楼梯 

这道题目力扣改了题目描述了,现在的题目描述清晰很多,相当于明确说 第一步是不用花费的。 

更改题目描述之后,相当于是 文章中 「拓展」的解法 

代码随想录

视频讲解:动态规划开更了!| LeetCode:746. 使用最小花费爬楼梯_哔哩哔哩_bilibili

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+1);//注意还有一个楼顶,所以dp会比cost多一个元素
        dp[0] = 0;
        dp[1] = 0;
        for(int i = 2; i <= cost.size(); i++){
            dp[i] = min(dp[i-2] + cost[i-2], dp[i-1] + cost[i-1]);
        }
        return dp[cost.size()];
    }
};

 62.不同路径 

本题大家掌握动态规划的方法就可以。 数论方法 有点非主流,很难想到。 

代码随想录

视频讲解:动态规划中如何初始化很重要!| LeetCode:62.不同路径_哔哩哔哩_bilibili

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n,0));//注意初始化方式;dp含义是原点到每个位置路径数量
        //初始化 边缘的位置只有一种路径可能性
        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];    
    }
};

 63. 不同路径 II

视频讲解:动态规划,这次遇到障碍了| LeetCode:63. 不同路径 II_哔哩哔哩_bilibili

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();//行数
        int n = obstacleGrid[0].size();//列数
        vector<vector<int>> dp(m , vector<int>(n, 0));
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0;
        //初始化
        for(int i = 0; i < m && obstacleGrid[i][0] != 1; i++) dp[i][0] = 1;
        //不用再为障碍后面的位置赋值了,因为初始化的时候默认就是0
        for(int j = 0; j < m && obstacleGrid[0][j] != 1; j++) dp[0][j] = 1;
        //动态规划
        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(obstacleGrid[i-1][j] == 1 && obstacleGrid[i][j-1] != 1) dp[i][j] = dp[i][j-1];
                else if(obstacleGrid[i][j-1] == 1 && obstacleGrid[i-1][j] != 1) dp[i][j] = dp[i-1][j];
                else if(obstacleGrid[i-1][j] == 1 && obstacleGrid[i][j-1] == 1) dp[i][j] = 0;
                else dp[i][j] = dp[i][j-1] + dp[i-1][j];
                // cout << dp[i][j] << endl;
            }
        }
        return dp[m-1][n-1];
    }
};

代码随想录里是这样写动态规划逻辑的,在此位置如果是障碍,就不计算dp了,然后后面照常计算

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();//行数
        int n = obstacleGrid[0].size();//列数
        vector<vector<int>> dp(m , vector<int>(n, 0));
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0;
        //初始化
        for(int i = 0; i < m && obstacleGrid[i][0] != 1; i++) dp[i][0] = 1;
        //不用再为障碍后面的位置赋值了,因为初始化的时候默认就是0
        for(int j = 0; j < m && obstacleGrid[0][j] != 1; j++) dp[0][j] = 1;
        //动态规划
        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(obstacleGrid[i-1][j] == 1 && obstacleGrid[i][j-1] != 1) dp[i][j] = dp[i][j-1];
                else if(obstacleGrid[i][j-1] == 1 && obstacleGrid[i-1][j] != 1) dp[i][j] = dp[i-1][j];
                else if(obstacleGrid[i-1][j] == 1 && obstacleGrid[i][j-1] == 1) dp[i][j] = 0;
                else dp[i][j] = dp[i][j-1] + dp[i-1][j];
                // cout << dp[i][j] << endl;
            }
        }
        return dp[m-1][n-1];
    }
};

 343. 整数拆分 

代码随想录

视频讲解:动态规划,本题关键在于理解递推公式!| LeetCode:343. 整数拆分_哔哩哔哩_bilibili

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1,0);
        dp[2] = 1;//1*1
        for(int i = 3; i <= n; i++){
            for(int j = 1; j <= i/2; j++){
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));//ATTENTION
            }
        }
        return dp[n];
    }
};

 96.不同的二叉搜索树 

代码随想录

视屏讲解:动态规划找到子状态之间的关系很重要!| LeetCode:96.不同的二叉搜索树_哔哩哔哩_bilibili

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1);
        dp[0] = 1;//ATTENTION
        // dp[1] = 1;
        // dp[2] = 2;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++){
                dp[i] += dp[j-1] * dp[i-j];//j == i的时候,得用到dp[0],必须得初始化一下
            }
        }
        return dp[n];
    }
};

背包问题

01背包问题 二维 

代码随想录

视频讲解:带你学透0-1背包问题!| 关于背包问题,你不清楚的地方,这里都讲了!| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili

代码以此题为例,背包最大重量为4,物品如下表所示,问背包能背的物品最大价值是多少?

重量价值
物品0115
物品1320
物品2430

1、dp[i][j] 的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

2、确定递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

其中dp[i - 1][j]是不放i的情况,dp[i - 1][j - weight[i]] + value[i]是放i的情况。可以看出dp[i][j] 是由左上方数值推导出来。

3、初始化

dp[i][0]:一定都是0

dp[0][j]:分情况,如果①当 j < weight[0]的时候,dp[0][j] 应该是 0;②当j >= weight[0]时,dp[0][j] 应该是value[0]

其他位置默认0就行了

——所以只需要考虑:当j >= weight[0]的情况,dp[0][j] 是value[0]

4、确定遍历顺序

先遍历 物品还是先遍历背包重量呢?其实都可以!! 但是先遍历物品更好理解。

5、举例推导dp数组

做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!

#include <vector>
#include <iostream>
using namespace std;
void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;
    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
    // 初始化
    for (int j = weight[0]; j <= bagweight; j++) {
        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() {
    test_2_wei_bag_problem1();
}

可以在线编译器中试一下:C++ 在线工具 | 菜鸟工具 (jyshare.com)

卡码网题目:

题目页面 (kamacoder.com)

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

void reseacher_material(){   
    int M, N;
    cin >> M >> N;
    vector<int> space(M, 0); // 存储每件物品所占空间
    vector<int> value(M, 0);  // 存储每件物品价值
    for(int i = 0; i < M; i++) cin >> space[i];
    for(int i = 0; i < M; i++) cin >> value[i];
    // define the dp
    vector<vector<int>> dp(M,vector<int>(N+1,0));
    // initation
    for(int j = space[0]; j <= N; j++){
        dp[0][j] = value[0];
    }
    // dynamic programming
    for(int i = 1; i < M; i++){
        for(int j = 0; j <= N; j++){
            if(j < space[i]) dp[i][j] = dp[i-1][j];
            else dp[i][j] = max(dp[i-1][j], dp[i-1][j-space[i]]+ value[i]);
        }
    }
    std::cout << dp[M-1][N] << std::endl;
}
int main(){
    reseacher_material();
}

01背包问题 一维(滚动数组)

代码随想录

视频讲解:带你学透01背包问题(滚动数组篇) | 从此对背包问题不再迷茫!_哔哩哔哩_bilibili

1、dp[i]的含义:容量为j的背包,所背的物品价值可以最大为dp[j]

2、递推公式:此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值。需要理解是上一层的拷贝

3、初始化:

假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了

4、一维dp数组遍历顺序:逆序遍历

5、举例推导dp数组

自编题

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);// just initiate 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();
}

卡码网

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

void reseacher_material(){
    int M, N;
    cin >> M >> N;
    vector<int> space(M); // 存储每件物品所占空间
    vector<int> value(M);  // 存储每件物品价值
    for(int i = 0; i < M; i++) cin >> space[i];
    for(int i = 0; i < M; i++) cin >> value[i];
    // define the dp
    vector<int> dp(N+1,0);
    // initation -- just 0
    // dynamic programming
    for(int i = 0; i < M; ++i){
        for(int j = N; j >= space[i]; --j){
			dp[j] = max(dp[j], dp[j-space[i]]+ value[i]);
        }
    }
    cout << dp[N] << endl;
}
int main(){
    reseacher_material();
}

416.分割等和子集  

本题是 01背包的应用类题目

代码随想录

题目链接:416. 分割等和子集 - 力扣(LeetCode)

视频讲解:动态规划之背包问题,这个包能装满吗?| LeetCode:416.分割等和子集_哔哩哔哩_bilibili

同类题型:
  • 698.划分为k个相等的子集
  • 473.火柴拼正方形

1、dp数组的含义:容量为j的背包,所背的物品价值最大可以为dp[j]。本题中每一个元素的数值既是重量,也是价值。套到本题,dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]

那么如果背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。

2、递推公式

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

3、初始化

// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);

4、遍历顺序

对于一维背包,j逆序

5、举例推导dp数组

dp[j]的数值一定是小于等于j的。

如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        // 也可以使用库函数一步求和
        // int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1) return false;
        int target = sum / 2;

        // 开始 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]);
            }
            cout <<dp[0]<<dp[1]<<dp[2]<<dp[3]<<dp[4]<<dp[5]<<dp[6]<<dp[7]<<dp[8]<<dp[9]<<dp[10]<<dp[11]<<endl;
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target) return true;
        return false;
    }
};
// 二维的版本
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;
        sum /= 2;
        vector<vector<int>> dp(nums.size(), vector<int>(sum + 1, 0));
        for(int j = nums[0]; j <= sum; j++){
            dp[0][j] = nums[0];
        }
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j <= sum; j++){
                if(j < nums[i]) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
            }
        }
        // return dp[nums.size()-1][sum];
        if(dp[nums.size()-1][sum] == sum) return true;
        return false;
    }
};

1049.最后一块石头的重量 II 

本题就和 昨天的 416. 分割等和子集 很像了,可以尝试先自己思考做一做。 

题目链接:1049最后一块石头的重量 - 力扣(LeetCode)

视频讲解:动态规划之背包问题,这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        
        int sum = 0;
        for(int i = 0; i < stones.size(); i ++){
            sum += stones[i];
        }
        int target = sum / 2;
        vector<int> dp(target + 1, 0);
         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];
    }
};

494.目标和 

大家重点理解 递推公式:dp[j] += dp[j - nums[i]],这个公式后面的提问 我们还会用到。  

left 表示将要设置符号为+的元素

right 表示将要设置符号为-的元素

left + right = sum

left - right = target

left - (sum - left) = target

left = (target + sum) / 2;

如果left不能整除,其实是凑不出来的,如果不能嗯整除就return 0 就行

转化成从nums挑选出和为left的元素们,有多少种可能

视频讲解:动态规划之背包问题,装满背包有多少种方法?| LeetCode:494.目标和_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        // vector<int> dp(501,0);
        int sum = 0;
        int left;
        for(int i = 0; i < nums.size(); i++) sum += nums[i];
        if((target + sum) % 2 == 1) return 0;
        left = (target + sum) / 2;
        vector<int> dp(left + 1, 0);
        dp[0] = 1; // ??

        for(int i = 0; i < nums.size(); i++){
            for(int j = left; j >= nums[i]; j--){
                // dp[j] = dp[j] + dp[j - nums[i]];
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[left];
    }
};

474.一和零  

通过这道题目,大家先粗略了解, 01背包,完全背包,多重背包的区别,不过不用细扣,因为后面 对于 完全背包,多重背包 还有单独讲解。

视频讲解:动态规划之背包问题,装满这个背包最多用多少个物品?| LeetCode:474.一和零_哔哩哔哩_bilibili

代码随想录

力扣上没有纯粹的完全背包的题目,所以大家看本篇了解一下 完全背包的理论 

后面的两道题目,都是完全背包的应用,做做感受一下 

 完全背包理论基础

视频讲解:带你学透完全背包问题! 和 01背包有什么差别?遍历顺序上有什么讲究?_哔哩哔哩_bilibili

https://programmercarl.com/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85.html 

 518.零钱兑换 II  

视频讲解:动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II_哔哩哔哩_bilibili

代码随想录

 377.组合总和 Ⅳ  

视频讲解:动态规划之完全背包,装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV_哔哩哔哩_bilibili

代码随想录

70. 爬楼梯 (进阶) 

这道题目 爬楼梯之前我们做过,这次再用完全背包的思路来分析一遍 

代码随想录

322. 零钱兑换  

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

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

这句话结合本题 大家要好好理解。

视频讲解:动态规划之完全背包,装满背包最少的物品件数是多少?| LeetCode:322.零钱兑换_哔哩哔哩_bilibili

代码随想录

279.完全平方数  

本题 和 322. 零钱兑换 基本是一样的,大家先自己尝试做一做 

视频讲解:动态规划之完全背包,换汤不换药!| LeetCode:279.完全平方数_哔哩哔哩_bilibili

代码随想录

139.单词拆分 

视频讲解:动态规划之完全背包,你的背包如何装满?| LeetCode:139.单词拆分_哔哩哔哩_bilibili

代码随想录

多重背包

你该了解这些! 

代码随想录

背包问题总结篇 

代码随想录

打家劫舍

198.打家劫舍 ——线性

代码链接:198. 打家劫舍 - 力扣(LeetCode)

视频讲解:动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili

代码随想录

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(), 0);
        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];
    }
};

213.打家劫舍II ——环形

视频讲解:动态规划,房间连成环了那还偷不偷呢?| LeetCode:213.打家劫舍II_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size()-2);
        int result2 = robRange(nums, 1, nums.size()-1);
        return max(result1, result2);

    }
    int robRange(vector<int>& nums, int start, int end){
        if(end == start) return nums[start];
        vector<int> dp(nums.size(), 0);
        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];
    }
};

337.打家劫舍III  ——二叉树

视频讲解:动态规划,房间连成树了,偷不偷呢?| LeetCode:337.打家劫舍3_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int rob(TreeNode* root) {
        //后序遍历
        //递归1 : 判断结束条件
        if(root == NULL) return 0;
        if(root->left == NULL && root->right == NULL) return root->val;

        //偷当前节点
        int val1 = root->val;
        if(root->left != NULL) val1 += rob(root->left->left) + rob(root->left->right);
        if(root->right != NULL) val1 += rob(root->right->left) + rob(root->right->right);
        //不偷当前节点
        int val2 = rob(root->left) + rob(root->right);
        return max(val1,val2);
    }
};
在visual studio中调试,注意二叉树的初始化方法
//#include <stddef.h>
#include <windows.h>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

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) {}
};

TreeNode* buildTree(const vector<int>& vals) {
if (vals.empty()) return nullptr;
TreeNode *root = new TreeNode(vals[0]);
queue<TreeNode*> q;
q.push(root);
size_t i = 1;
while (!q.empty() && i < vals.size()) {
	TreeNode *node = q.front();
	q.pop();

	if (vals[i] != NULL) {
		node->left = new TreeNode(vals[i]);
		q.push(node->left);
	}
	++i;

	if (i < vals.size() && vals[i] != NULL) {
		node->right = new TreeNode(vals[i]);
		q.push(node->right);
	}
	++i;
}

return root;
}

class Solution {
public:
	int rob(TreeNode* root) {
		//后序遍历
		//递归1 : 判断结束条件
		if (root == NULL) return 0;
		if (root->left == NULL && root->right == NULL) return root->val;

		//偷当前节点
		int val1 = root->val;
		if (root->left != NULL) val1 += rob(root->left->left) + rob(root->left->right);
		if (root->right != NULL) val1 += rob(root->right->left) + rob(root->right->right);
		//不偷当前节点
		int val2 = rob(root->left) + rob(root->right);
		return max(val1, val2);
	}
};

//TreeNode *input= new TreeNode(3);
int main() {
	//定义二叉树
	//方法一:
	//vector<int> values = { 3, 2, 3, NULL, 3, NULL, 1 };
	//TreeNode *root = buildTree(values);
	//方法二:
	TreeNode* root = new TreeNode(3);
	root->left = new TreeNode(2);
	root->right = new TreeNode(3);
	root->left->right = new TreeNode(3);
	root->right->right = new TreeNode(1);


	Solution ceshi;
	std::cout << ceshi.rob(root) << std::endl;
}

买卖股票的最佳时机

121. 买卖股票的最佳时机 

视频讲解:动态规划之 LeetCode:121.买卖股票的最佳时机1_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        vector<vector<int>> dp(prices.size(),vector<int>(2, 0));
        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-1][0]+prices[i]);
        }
        return dp[prices.size()-1][1];
    }
};

122.买卖股票的最佳时机II  

视频讲解:动态规划,股票问题第二弹 | LeetCode:122.买卖股票的最佳时机II_哔哩哔哩_bilibili

代码随想录

方法一:贪心算法

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

方法二:动态规划

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

123.买卖股票的最佳时机III  

这道题一下子就难度上来了,关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。

视频讲解:动态规划,股票至多买卖两次,怎么求? | LeetCode:123.买卖股票最佳时机III_哔哩哔哩_bilibili

代码随想录

188.买卖股票的最佳时机IV  

本题是123.买卖股票的最佳时机III 的进阶版  

视频讲解:动态规划来决定最佳时机,至多可以买卖K次!| LeetCode:188.买卖股票最佳时机4_哔哩哔哩_bilibili

代码随想录

309.最佳买卖股票时机含冷冻期 

本题加了一个冷冻期,状态就多了,有点难度,大家要把各个状态分清,思路才能清晰 

视频讲解:动态规划来决定最佳时机,这次有冷冻期!| LeetCode:309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili

代码随想录

714.买卖股票的最佳时机含手续费  

相对122.买卖股票的最佳时机II ,本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的,可以尝试自己做一做。

视频讲解:动态规划来决定最佳时机,这次含手续费!| LeetCode:714.买卖股票的最佳时机含手续费_哔哩哔哩_bilibili

代码随想录

子序列

一种是一个序列里的递增个数,一种是两个序列里的公共个数

一种问法是子序列,是可以索引不连续的;一种问法是要求索引连续的

注意dp的定义,常常需要要求以nums[i]为结尾之类的,因此还需要res取最大值

300.最长递增子序列 

今天开始正式子序列系列,本题是比较简单的,感受感受一下子序列题目的思路。 

视频讲解:动态规划之子序列问题,元素不连续!| LeetCode:300.最长递增子序列_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        int n = nums.size();
        int res = 1;
        vector<int> dp(n, 1);
        for(int i = 0; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    dp[i] = max(dp[i], dp[j] + 1);//取dp[j] + 1的最大值
                }
            }
            res = res > dp[i] ? res : dp[i];
        }
        return res;
    }
};

674. 最长连续递增序列 

本题相对于昨天的动态规划:300.最长递增子序列 最大的区别在于“连续”。 先尝试自己做做,感受一下区别  

视频讲解:动态规划之子序列问题,重点在于连续!| LeetCode:674.最长连续递增序列_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int res = 1;
        if(nums.size() == 0) return 0;
        vector<int> dp(nums.size(), 1);
        for(int i = 1; i < nums.size(); i ++){
            if(nums[i] > nums[i-1]){
                dp[i] = dp[i - 1] + 1;
            }else{}
            res = res > dp[i] ? res : dp[i];
        }
        return res;
    }
};

718. 最长重复子数组 

稍有难度,要使用二维dp数组了

视频讲解:动态规划之子序列问题,想清楚DP数组的定义 | LeetCode:718.最长重复子数组_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        // 定义dp + 初始化0
        int res = 0;
        vector<vector<int>> dp(nums1.size() + 1,vector<int>(nums2.size() + 1,0));
        for(int i = 1; i < nums1.size() + 1; i++){
            for(int j = 1; j < nums2.size() + 1; j++){
                if(nums1[i - 1] == nums2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    if(res < dp[i][j]) res = dp[i][j];
                }
            }
        }
        return res;
    }
};

1143.最长公共子序列 

体会一下本题和 718. 最长重复子数组 的区别  

视频讲解:动态规划子序列问题经典题目 | LeetCode:1143.最长公共子序列_哔哩哔哩_bilibili

代码随想录

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.size(), n2 = text2.size();
        vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1, 0));
        int res = 0;
        for(int i = 1; i <= n1; i++){
            for(int j = 1; j <= n2; j++){
                if(text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
        return dp[n1][n2];
    }
};
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        // initial dp
        int res = 0;
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        // 递推
        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;
                    if(res < dp[i][j])  res = dp[i][j];
                }else{
                    dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);//不好想
                    if(res < dp[i][j])  res = dp[i][j];
                }
            }
        }
        return res;
    }
};

1035.不相交的线 

其实本题和 1143.最长公共子序列 是一模一样的,大家尝试自己做一做。

视频讲解:动态规划之子序列问题,换汤不换药 | LeetCode:1035.不相交的线_哔哩哔哩_bilibili

代码随想录

53. 最大子序和 

这道题我们用贪心做过,这次 再用dp来做一遍 

视频讲解:看起来复杂,其实是简单动态规划 | LeetCode:53.最大子序和_哔哩哔哩_bilibili

代码随想录

贪心算法:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN ;
        int sum = 0 ;
        for(int i = 0; i < nums.size(); i++){
            sum = sum + nums[i]; 
            if(sum < 0){
                sum = 0;
            }else{
                res = res > sum ? res : sum;
            }
        }
        return res ;
    }
};

动态规划:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        vector<int> dp(n, 0);
        dp[0] = nums[0];
        int res = nums[0];
        for(int i = 1; i < n; i++){
            dp[i] = max(dp[i-1] + nums[i], nums[i]);
            res = res > dp[i] ? res : dp[i];
        }
        return res;
    }
};

编辑距离

392.判断子序列 

这道题目算是 编辑距离问题 的入门题目(毕竟这里只是涉及到减法),慢慢的,后面就要来解决真正的 编辑距离问题了

代码随想录

动态规划

双指针

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n = s.length(), m = t.length();
        int i = 0, j = 0;
        while (i < n && j < m) {
            if (s[i] == t[j]) {
                i++;
            }
            j++;
        }
        return i == n;
    }
};

我的

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

115.不同的子序列 

但相对于刚讲过 392.判断子序列,本题 就有难度了 ,感受一下本题和  392.判断子序列 的区别。 

代码随想录

583. 两个字符串的删除操作 

本题和动态规划:115.不同的子序列 相比,其实就是两个字符串都可以删除了,情况虽说复杂一些,但整体思路是不变的。

代码随想录

72. 编辑距离 

最终我们迎来了编辑距离这道题目,之前安排题目都是为了 编辑距离做铺垫。 

代码随想录

编辑距离总结篇 

做一个总结吧

代码随想录

647. 回文子串   

动态规划解决的经典题目,如果没接触过的话,别硬想 直接看题解。

代码随想录 (programmercarl.com)

647. 回文子串

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        int res = 0;
        if(n < 2) return n;
        // n = 2,3,4...
        // dp[i][j] = dp[i+1]dp[j-1] & s[i] == s[j]
        //定义dp
        vector<vector<bool>> dp(n,vector<bool>(n, false));//行数,列数,赋值false
        for(int i = n - 1; i >= 0; i --){
            for(int j = i; j < n; j++){
                if(s[i] == s[j]){
                    if(j - i < 2){//长度为1的子串一定是回文!如果s[i]==s[j]那么长度为2的也一定是子串!
                        dp[i][j] = true;
                        res ++;
                    }else if(dp[i + 1][j - 1] == true){
                        dp[i][j] = true;
                        res ++;
                    }     
                }
           
            }
        }        
        return res;
    }
};

5. 最长回文子串

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        //int res = 0;
        int maxStr = 1;//只要n >= 2,最小长度最小就是1
        int begin = 0;//用来记起始位置
        if(n < 2) return s;
        // n = 2,3,4...
        // dp[i][j] = dp[i+1]dp[j-1] & s[i] == s[j]
        //定义dp
        vector<vector<bool>> dp(n,vector<bool>(n, false));//行数,列数,赋值false
        for(int i = n - 1; i >= 0; i --){
            for(int j = i; j < n; j++){
                if(s[i] == s[j]){
                    if(j - i < 2){//长度为1的子串一定是回文!如果s[i]==s[j]那么长度为2的也一定是子串!
                        dp[i][j] = true;
                        if(maxStr < j - i + 1){
                            maxStr = j - i + 1;
                            begin = i;
                        }

                        //res ++;
                    }else if(dp[i + 1][j - 1] == true){
                        dp[i][j] = true;
                        if(maxStr < j - i + 1){
                            maxStr = j - i + 1;
                            begin = i;
                        }
                        //res ++;
                    }     
                }
            }
        }        
        return s.substr(begin, maxStr);
    }
};
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        int maxLen = 1;//就算所有字符都不同,那么最小也是1!
        //比如ab,dp[1][2] = false,但是子串长度是1
        int begin = 0;
        // 考虑 空字符串 与 单个字符
        if(n < 2) return s;
        // 定义dp
        vector<vector<int>> dp(n, vector<int>(n));//dp[i][j]表示s[i..j]是否为回文串
        // 初始化 所有长度为1的子串都是回文串
        for(int i = 0; i < s.size(); i++){
            dp[i][i] = true;
        }
        //枚举子串长度
        for(int L = 2; L <= n; L++){
            //枚举左边界
            for(int i = 0; i < n; i++){
                int j = i + L - 1;//左闭右闭
                if(j >= n) break; //如果右边界过界,跳出循环
                if(s[i] != s[j]) dp[i][j] = false;//边界不满足跳出循环
                else{//边界满足
                    if(j - i < 3){//L = 0, 1, 2,也是为了写递推函数的时候不越界
                        dp[i][j] = true;
                    }else{
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                // 统计回文子串的长度和起点位置
                if(dp[i][j] == true && maxLen < L){
                    maxLen = L ;
                    begin = i;
                }
            }
        }
        return s.substr(begin, maxLen);
    }
};

516.最长回文子序列 

 647. 回文子串,求的是回文子串,而本题要求的是回文子序列, 大家要搞清楚两者之间的区别。 

https://programmercarl.com/0516.%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.html

动态规划总结篇 

https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E6%80%BB%E7%BB%93%E7%AF%87.html

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值