代码随想录算法训练营第四十四天|完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

代码随想录 (programmercarl.com)

完全背包

完全背包的物品数量不受限制(正序遍历物品),0-1背包每个物品只能使用一次(倒序遍历背包)

完全背包两个for循环可以颠倒顺序,一维0-1背包问题只能先遍历物品再遍历背包。

518. 零钱兑换 II

类似于前面0-1背包的494.目标和问题

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

dp[j]:凑成总金额j的货币组合数为dp[j]

2.确定递推公式

有多少种方法用的都是这个递推公式:dp[j] += dp[j - coins[i]];

3.dp数组如何初始化

dp[0] = 1是 递归公式的基础,如果dp[0] = 0 的话,后面所有推导出来的值都是0了。

后台测试数据是默认,amount = 0 的情况,组合数为1。

4.确定遍历顺序

不同于纯完全背包问题,遍历顺序两个for循环可以调换顺序,此处不可以。

纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行。而本题要求凑成总和的组合数,元素之间明确要求没有顺序。所以纯完全背包是能凑成总和就行,不用管怎么凑的。

但是本题是求凑出来的方案个数,且每个方案个数是为组合数。

所以本题就需要注意遍历顺序,以下分情况讨论,以amount=5和coins=[1,2,5]为例:

1)先遍历物品再遍历背包:

物品按照遍历顺序出现{1,2},不会出现{1,2}{2,1}同时出现的情况,即得出的是组合数

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

2)先遍历背包再遍历物品:

遍历背包后物品每次都会再重新遍历一遍,会出现{1,2}{2,1}同时出现的情况,即得出的是排列数

for (int j = 0; j <= amount; j++) { // 遍历背包容量
    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
    }
}

打印结果:

dp[j] j = 0 1 2 3 4 5
i = 0 1 0 0 0 0 0
1 1 1 2

3

i = 0:

dp[3] += dp[3-1]==>dp[3] = 2==>[1,1,1][1,2]

i = 1:

dp[3] += dp[3-2]==>dp[3] = 2 + 1 = 3==>[2,1]

此处j = 3的情况中,就出现了[1,1,1][1,2][2,1]

所以为排列数,不符合题意

2
class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
//注意j的起始大小,回顾dp[j]含义可知,j为背包容量,
//要确保其大于物品的重量,即需要从coins[i]开始遍历,而不是coins[0]
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
}

注意j的起始大小,回顾dp[j]含义可知,j为背包容量,要确保其大于物品的重量,即需要从coins[i]开始遍历,而不是coins[0]。

377. 组合总和 Ⅳ  

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

dp[i]:凑成目标正整数为i的排列个数为dp[i]

2.确定递推公式

有多少种方法用的都是这个递推公式:dp[j] += dp[j - coins[i]];

3.dp数组如何初始化

题目中说给定目标值是正整数, 所以dp[0] = 1是没有意义的,此处的初始化仅仅是为了推导递推公式。

4.确定遍历顺序

求的是排列数,所以选择先遍历背包再遍历物品的顺序。

如果求组合数就是先物品再背包,如果求排列数就是先背包再物品。

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

【拓展爬楼梯】

原始==一步只能爬1,2个台阶;进阶==一步可以爬m个台阶,到楼顶有多少种爬楼方式,代码和本题一致,求解排列数。

你可能感兴趣的:(算法,leetcode,开发语言,数据结构,java)