完全背包之零钱兑换II

        在上一篇文章中,分享了零钱兑换I,这篇文章分享零钱兑换II。

零钱兑换II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1; // 装满容量为0的背包有一种方法
        for(int i = 0;i < coins.length;i++){
            for(int j = 0;j <= amount;j++){
                if(j >= coins[i]){
                    dp[j] += dp[j - coins[i]];
                }
            }
        }
        return dp[amount];
    }
}

        零钱兑换I是恰好装满背包,最少的物品数量,此题相当于是恰好装满背包,有多少种装法,这里的硬币面值相当于是物品的体积,也是价值,其实这两道题和物品价值已经没有太大关系。这道题要求求恰好转满背包的组合数,首先需要知道dp[j]的含义就是背包容量为j,有dp[j]种装满背包的方法,放在这道题上就是总金额为j,有dp[j]种方法凑出总金额j。遍历到面值coins[i]时,如果存在一种硬币组合的金额之和等于 j−coins[i],则在该硬币组合中增加一个面额为coins[i]的硬币,即可得到一种金额之和等于j的硬币组合,举个例子,总金额为5,遍历到面值3,如果存在硬币组合的金额为5 - 3等于2,那么在该组合中添加面额为3的硬币,就可以得到金额之和为5的硬币组合,因此需要遍历coins数组,找出所有j - coins[i]的组合,相加就是总金额为j的所有硬币组合,得到dp[j] += dp[j - coins[i]],这里需要初始化dp[0] = 1,就是总金额为0,凑出总金额为0的面值有一种方法,为什么等会再说。还有一点需要强调的就是这种恰好问题,往往需要判断j - coins[i]是否等于dp数组的初始化值,这题不需要判断,是因为有钱币面值1,不管总金额为多少,都可以通过面额为1的硬币凑出。
        现在以上述示例说明代码的执行过程。dp数组初始化如下:
在这里插入图片描述        首先遍历面值coins[0] = 1,当总金额为j = 1时,j >= coins[i],dp[j] += dp[j - coins[i]],也就是dp[1] += dp[1 - coins[0]],dp[1] += dp[0] = 1,这里就涉及到dp[0]的值的问题,这里明显只能初始化为1,初始化为其他值,无法得到正确答案。当总金额为j = 2时,dp[2] += dp[j - coins[0]] = dp[2 - 1] = 1,同理得dp[5] = dp[4] = dp[3] = dp[2] = 1,因为仅有面额为1的硬币,凑出任意金额的方法仅有1种,遍历完面额1,dp数组如下:
在这里插入图片描述
        接着遍历面额2,当总金额为0和1,都不会进入if,当j = 2时,j - coins[1] = 2 - 2 = 0,dp[2] += dp[0] = 2,当j = 3时,j - coins[1] = 3 - 2 = 1,dp[3] += dp[1] = 2,dp[4] += dp[4 - 2] = 3,dp[5] += dp[5 - 2] = 3,遍历完面值2,dp数组如下:
在这里插入图片描述

        遍历面额为5时,当j = 0,1,2,3,4都不会进入if,当j = 5时,dp[5] += dp[5 - 5] = 4,面额为5遍历完,dp数组中如下,最后返回dp[amount],即dp[5]为最终结果。
在这里插入图片描述
        还没有完哈,还有一点需要说明,当先遍历物品的数量,后遍历背包的体积,得到是组合数,如果两侧循环颠倒,先遍历背包的体积,后遍历物品的数量,得到的就是排列数,下一题组合总和IV和这一题的区别就是两层循环的是颠倒的,其余完全相同。为什么呢?上面遍历过程,总是横向遍历,把面额为1的所有情况遍历完,才会遍历面额为2,面额为5,有一定的顺序,面额为1只能考虑了面额为1的情况,面额为2只能考虑了面额为1和面额为2的情况,面额为5才能考虑了面额为1,2和5的情况。以组合总和IV如何纵向遍历,实现排列数。

组合总和IV

给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。题目数据保证答案符合 32 位整数范围。

示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for(int j = 0;j <= target;j++){
            for(int i = 0;i < nums.length;i++){
                if(j >= nums[i]) dp[j] += dp[j - nums[i]];
            }
        }
        return dp[target];
    }
}

        组合总和和零钱兑换可以说是一模一样,目标值相当于是总金额,组合总和给出的数组相当于是面额数组,首先dp数组初始化入如下:
在这里插入图片描述
        纵向遍历,j等于0,不会进入if条件,j等于1时,第二层循环考虑了数组所有元素,仅元素1可以进入if,dp[1] += dp[1 - 1] = 1,dp数组如下:
在这里插入图片描述
        j = 2时,第二层循环也考虑了所有元素,元素1和元素2可以进入if,dp[2] += dp[2 - 1],也就是dp[2] += dp[1],dp[2] += dp[0],而dp[1]是上一层循环,考虑了所有元素得到的,不像横向遍历,仅能考虑当前物品的之前物品,而纵向遍历则可以考虑所有物品,所以纵向遍历可以得到排列数,j = 2遍历完得到dp数组如下:
在这里插入图片描述
        之后的情况就不展开详细说明,直接给出dp的结果,j = 3,dp数组如下:
在这里插入图片描述
        j = 4,dp数组如下:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值