打卡 | 动态规划 - 完全背包、518. 零钱兑换 II、 377. 组合总和 Ⅳ

文章目录

  • 完全背包
  • 零钱兑换 II
  • 377. 组合总和 Ⅳ

完全背包

相对于01背包的区别,物品有无限个,可以无限取。

01背包和完全背包唯一不同就是体现在遍历顺序上,所以本文就不去做动规五部曲了,我们直接针对遍历顺序经行分析!

01背包核心代码:

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

01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。

而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

public static int fullBag2(int m, int bagSize, int[] w, int[] v) {
        int[] dp = new int[bagSize+1];

        for (int i = 1; i <= bagSize; i++) { // 背包容量
            for (int j = 0; j < m; j++) { // 物品
                if (i - w[j] >= 0) {
                    dp[j] = Math.max(dp[i], dp[i - w[j]] + v[j]);
                }
            }
        }

        // System.out.println(Arrays.toString(dp));

        return dp[bagSize];
    }

零钱兑换 II

打卡 | 动态规划 - 完全背包、518. 零钱兑换 II、 377. 组合总和 Ⅳ_第1张图片

零钱兑换 可以转化成一个完全背包问题。

即, 装满 amount 大小的 “背包”,有多少种装法。

动规五部曲:

  1. dp[j]:凑成总金额 j 的货币组合数为dp[j]
  2. dp[j] += dp[j - coins[i]]
  3. dp[0] = 1,
    dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。
  4. 遍历顺序:
    刚刚提到了,完全背包和01背包的区别就在于遍历方式。
    完全背包,内层也是正序遍历。

先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

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

假设:coins[0] = 1,coins[1] = 5。
那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!

如果把两个for交换顺序,代码如下:

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

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。
此时dp[j]里算出来的就是排列数!

即,
外层遍历物品,内层遍历背包,即求组合数。
外层遍历背包,内层遍历物品,即求排列数。

  1. 举例推导dp数组
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++) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
}

377. 组合总和 Ⅳ

打卡 | 动态规划 - 完全背包、518. 零钱兑换 II、 377. 组合总和 Ⅳ_第2张图片

这道题和上一题的核心思路是一样的,只不过此题的结果中需要考虑元素的顺序,那么就是求排列数。

外层遍历物品,内层遍历背包,即求组合数。
外层遍历背包,内层遍历物品,即求排列数。

那么遍历顺序就需要变一变

外层遍历背包 target,内层遍历物品(nums)

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]];
                }
            }
            // System.out.println(Arrays.toString(dp));
        }

        return dp[target];
    }
}

你可能感兴趣的:(动态规划,算法)