动态规划5:组合总和Ⅳ、爬楼梯(进阶版)、零钱兑换、完全平方数、第五节总结

21. 组合总和 Ⅳ

例题377:
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

在这里插入图片描述
在这里插入图片描述
由于数组中每个元素可以重复取,所以是完全背包问题。
又因为不同序列可以视为不同的组合,所以这是完全背包的排列问题,因此遍历应该是先背包后物品。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。

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

代码如下:

时间复杂度O(target*nums.length)
空间复杂度O(target)
class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp=new int[target+1];
        dp[0]=1;
        for(int i=0;i<=target;i++){//背包
            for(int j=0;j<nums.length;j++){//物品
                if(i-nums[j]>=0) dp[i]+=dp[i-nums[j]];
            }
        }
        return dp[target];
    }
}

完全背包的组合与排列问题,就是遍历物品和背包的顺序不同!

22. 爬楼梯(进阶版)

例题70(爬楼梯非进阶版):
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

在这里插入图片描述
非进阶版是1步和2步可以重复取。

  1. dp[j]表示爬到第j阶台阶的不同方法。
  2. 递推公式
    dp[j]可以由dp[j-1]和dp[j-2]确定。也就是当在j-1台阶时,再上一阶就可以。
    当在j-2台阶时,再上两阶就可以。
    因此,dp[j]=dp[j-1]+dp[j-2]
  3. 初始化dp[0]=1,dp[1]=1,由结果反推,从2开始遍历

简单版爬楼梯代码如下:

递归方法
class Solution {
    public int climbStairs(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
}
}

进阶版爬楼梯描述如下:
改为一步一个台阶,两个台阶,三个台阶,…,直到m个台阶。问有多少种不同的方法可以爬到楼顶。

这个题可以转换为完全背包,也就是从1-m个数中重复选取和为n的集合个数。
注意,{1,m}和{m,1}是两个不同的集合,也就是完全背包中的排列问题。
那么,n就是背包容量,m就是物品。
代码如下:

时间复杂度O(m*n)
空间复杂度O(n)
class Solution {
    public int climbStairs(int m,int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(i-j>=0) dp[i]+=dp[i-j];
            }
        }
        return dp[n];
    }
}

23. 零钱兑换

例题322:
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。
在这里插入图片描述
因为硬币的个数是无限的,所以是完全背包。

  1. 确定dp数组和下标含义
    dp[j]表示凑成总金额j的最少硬币数。
  2. 确定递推公式
    dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
  3. 初始化
    dp[0]=0。
    由于每次dp[j]要取最小值,所以dp数组应该初始化为int的最大值。
  4. 确定遍历方向
    因为硬币的排列和种类无关,所以先物品后背包的方向遍历,并且背包是正向遍历。
  5. 举例验证
    代码如下:
class Solution {
    public int coinChange(int[] coins, int amount) {
        if(amount==0) return 0;
        int[] dp=new int[amount+1];
        int ma=Integer.MAX_VALUE;//自动拆箱
        dp[0]=0;
        for(int i=1;i<=amount;i++){
             dp[i]=ma;
        }
        for(int i=0;i<coins.length;i++){
            for(int j=coins[i];j<=amount;j++){
                if(dp[j-coins[i]]!=ma)//只有上一次选择不是不可达才判断
                dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
            }
        }
        if(dp[amount]==ma){
            return -1;
        }
        else{
        return dp[amount];
        }
    }
}

注意:

  1. 由于是求最小值,与之前的最大价值不同,需要用Math.min
  2. dp[j]只有在上一个dp[j-coins[i]]!=ma时才判断,也就是上一个金额是可达的。

24. 完全平方数

例题279:
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
在这里插入图片描述
由于数可以重复取值,因此是完全背包问题。

  1. 确定dp数组和下标定义
    dp[j]表示和为j的完全平方数的最少数量。
  2. 确定递推公式
    dp[j]由上一个dp[j-ii]决定
    dp[j]=Math.min(dp[j],dp[j-i
    i]+1);
  3. 初始化
    dp[0]=0;
  4. 确定递推方向
    由于选取数的顺序对结果没有影响,因此是先物品后背包的组合问题
  5. 举例验证
    代码如下:
class Solution {
    public int numSquares(int n) {
        int[] dp=new int[n+1];
        dp[0]=0;
        int ma=Integer.MAX_VALUE;
        for(int i=1;i<=n;i++){
            dp[i]=ma;
        }
        for(int i=1;i<=Math.sqrt(n);i++){
            for(int j=i*i;j<=n;j++){
                if(dp[j-i*i]!=ma)
                dp[j]=Math.min(dp[j],dp[j-i*i]+1);
            }
        }
        return dp[n];
    }
}

25. 第五节总结

  1. 组合总和Ⅳ
    给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。
    这个题就是完全背包的排列问题,需要先遍历背包再是物品
  2. 爬楼梯(进阶版)
    其实就是一个完全背包问题,在1-m的数中重复选择和为n的排列组合。
  3. 零钱兑换
    给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。
    这个题需要注意的是求最少个数。且当前个数依赖于减去当前硬币的个数加一。
    需要判断前一个是否可达,如果前一个都不可达,那么就不用判断增加
  4. 完全平方数
    给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。
    这个题与上一题类似,都是完全背包的最小组合问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值