Leetcode-动态规划

动态规划5部曲

  • 确定dp数组以及下标的含义
  • 确定递推公式
  • dp数组如何进行初始化,如dp[0]、dp[1],以及边界条件
  • 确定遍历顺序
  • 举例推导dp数组

Leetcode题目

1.斐波那契数

斐波那契数

  • dp数组代表最后的数列和
  • 递推公式dp[n]=dp[n-1]+dp[n-2]
  • 初始化dp[0]=0,dp[1]=1,
  • 遍历顺序从左向右遍历,for循环从i=2开始
  • 声明数组dp长度为n+1大小,最后返回dp[n]
    注:声明数组长度为n+1是因为数组索引是从0开始的,要想返回dp[n],必须要声明n+1长度的数组。
class Solution {
public:
    int fib(int n) {
        if(n<=1){
            return n;
        }
        vector<int>dp(n+1,0);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

2.爬楼梯

  • dp代表有多少中方法爬到楼顶
  • 递推公式dp[n]=dp[n-1]+dp[n-2]
  • 初始化这时不要使用dp[0]了,容易混淆概念,先初始化一个长度为n+1的数组dp(n+1,0),再初始化dp[1]=1,dp[2]=2;
  • for循环遍历从左到右,初始化int i=3,i<=n.

70. 爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        if(n<=1){
            return n;
        }
        vector<int>dp(n+1,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];
    }
};

3.爬楼梯-最小花费

746. 使用最小花费爬楼梯

  • dp下标含义为最小花费
  • 递推公式为dp[i]=min(dp[i-1],dp[i-2])+cost[i];注意这里是比较dp[i-1]和dp[i-2]之间最小值再加上cost[i]
  • 初始化dp[0]=cost[0],dp[1]=cost[1],声明数组dp大小为n
  • 最后返回return min(dp[n-1],dp[n-2]);为什么要比较n-1和n-2位置呢
    以cost=[10,15,20]为例:
dp[0]=10
dp[1]=15
dp[2]=min(dp[0],dp[1])+cost[2]=30,此时等于30这不是最小花费,最小花费是要将n-1和n-2处花费进行比较
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n=cost.size();
        vector<int>dp(n);
        dp[0]=cost[0];
        dp[1]=cost[1];
        for(int i=2;i<n;i++){
            dp[i]=min(dp[i-1],dp[i-2])+cost[i];
        }
        return min(dp[n-1],dp[n-2]);
    }
};

4.不同路径

  • dp数组代表路径数
  • 递推公式dp[m][n]=dp[m-1][n]+dp[m][n-1]
  • 初始化由于左边一列和上边一行都只有一条路径才能到达,因此全部初始化为1
    注:二维数组初始化vecor<vector>dp(m,vector(n,0))
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>>dp(m,vector<int>(n,0));
        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];
    }
};

4.不同路径||

  • dp数组下标代表路径数
  • 递推公式dp[m][n]=dp[m-1][n]+dp[m][n-1]
  • 这道题主要考虑遇到障碍怎么处理?
    初始化时如果遇到障碍怎么处理?障碍值为1,如果grid值为1你那么立即终止循环,后面的路径数应该都为0.
    比如初始化列我给出两段代码:
    遇到grid值为1初始化为0,值为0初始化为1
 for(int j=0;j<n;j++){
            if(obstacleGrid[0][j]==1){
                dp[0][j]=0;
            }
            dp[0][j]=1;
        }

在循环体判断条件加上如果grid值为0才给与初始化,否则直接退出循环,这才是正确的代码,因为如果遇到障碍1后面的路都不通了,路径数就到此为止了。

for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++){
 dp[0][j] = 1;
}

双层for循环代码细节:遇到障碍为1时直接continue下一次循环。

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

5.整数拆分

  • dp数组代表乘积和
  • 递推公式:dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]))
  • 由于0和1拆分没有任何意义,因此从dp[2]=1开始初始化
  • for循环遍历初始化i=3,j=1,j<i作为内层循环判断条件
class Solution {
public:
    int integerBreak(int n) {
        vector<int>dp(n+1,0);
        dp[2]=1;
        for(int i=3;i<=n;i++){
            for(int j=1;j<i;j++){
                dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }
};
}

6.分割等和子集

分割等和子集

  • dp数组代表数组元素和
  • 递推公式采用一维滚动数组dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])
  • 初始化dp[0]=0;声明数组长度为10001,防止数组加在一起超过20000除以2,注:在动态规划里数组长度可以随便声明多长,只是占用空间复杂度罢了。
  • for循环外层循环遍历物品,内层循环遍历背包,且内层循环是从后往前进行循环。
  • 最后如果dp[bagweight]=bagweight,return true;
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int bagweight=0;
        for(int i=0;i<nums.size();i++){
            bagweight+=nums[i];
        }
        if(bagweight %2==1){
            return false;
        }
        bagweight=bagweight/2;
        vector<int>dp(10001,0);
        dp[0]=0;
        for(int i=0;i<nums.size();i++){
            for(int j=bagweight;j>=nums[i];j--){
                dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        if(dp[bagweight]==bagweight){
            return true;
        }
        return false;
        }
};

7.目标和

目标和

  • 这道题是0-1背包的组合问题,之前的0-1背包解决的是背包最多能放多少物品,而这道题解决的是装满背包有多少中方法,且每个数只能取一次。类似组合问题:dp[j]+=dp[j-nums[i]];
  • dp数组代表方法数
  • 递推公式:dp[j]+=dp[j-nums[i]];
  • 初始化dp[0]=1;
  • 计算:正子集left,负子集sum-left,正子集-负子集=target,left-(sum-left)=target,得到left=(sum+target)/2;判断:if(abs(target)>sum){ return 0; } if((sum+target)%2==1){ return 0; }
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;
        }
        if((sum+target)%2==1){
            return 0;
        }
        int bagsize=(sum+target)/2;
        vector<int>dp(bagsize+1,0);
        dp[0]=1;
        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];
    }
};

8.零钱兑换||

  • 这是一道完全背包问题,因为物品可以一直加入。注意遍历顺序
  • dp数组代表组合数:组合不强调组合的顺序。排列强调组合的顺序
  • dp数组初始化dp[0]=1
  • 递推公式求组合数dp[j]+=dp[j-coins[i]]
  • 遍历顺序外层物品开始遍历,内层遍历背包容量从小到大遍历
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];
    }
};

9.组合总和IV

377. 组合总和 Ⅳ

  • 求组合数外层for循环遍历物品,内层for循环遍历背包
  • 求排列数外层for循环遍历背包,内层for循环遍历物品
  • do数组代表组合数
  • dp[0]=1
  • 递推公式dp[i]+=dp[i-nums[i]]
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for (int i = 0; i <= target; i++) { // 遍历背包
            for (int j = 0; j < nums.size(); j++) { // 遍历物品
                if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值