代码随想录第44天|完全背包,377. 组合总和 Ⅳ,518.零钱兑换II

完全背包问题

有N件物品和一个最多能背重量为W的背包。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

01背包和完全背包唯一不同就是体现在遍历顺序上

完全背包因为允许一件物品被取多次,所以是正序遍历而非倒序遍历

完全背包先遍历物品还是先遍历背包无所谓,但是如果是先遍历背包再遍历物品那就需要做一个判断  if (i - weight[j] >= 0)

1.dp含义

dp[j]:背包容量为j时背包的最大价值

2.dp初始化

3.递推公式

和01背包一模一样

  dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);

4.遍历方式

正序遍历,先遍历背包或者物品都无所谓

5.模拟推导

代码随想录第44天|完全背包,377. 组合总和 Ⅳ,518.零钱兑换II_第1张图片

代码实现

//先遍历物品,再遍历背包
private static void testCompletePack(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 0; i < weight.length; i++){ // 遍历物品
        for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
            dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
    }
}

//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
    int[] weight = {1, 3, 4};
    int[] value = {15, 20, 30};
    int bagWeight = 4;
    int[] dp = new int[bagWeight + 1];
    for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
        for (int j = 0; j < weight.length; j++){ // 遍历物品
            if (i - weight[j] >= 0){
                dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
            }
        }
    }
    for (int maxValue : dp){
        System.out.println(maxValue + "   ");
    }
}

最后,又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么? 这个简单的完全背包问题,估计就可以难住不少候选人了

518.零钱兑换II

思路

完全背包问题:我们求的组成amount的组合数,amount相当于背包容量,物品价值和物品重量都是coins[i]

1.dp含义

dp[i]表示凑成总金额为i的货币组合数

2.初始化dp

dp[0]=1,因为后台测试数据默认amount=0时组合数是为1的

3.递推公式

求的是组合数,dp[j]就等于所有的dp[j - coins[i]](考虑coins[i]的情况)相加。

4.遍历顺序

先遍历物品还是现遍历背包呢,都可以,这里我是先遍历物品,但是要判断coins[i]与我们当前遍历的背包的容量j的大小关系,这有点类似于剪枝操作

代码实现

class Solution {
    public int change(int amount, int[] coins) {
        //完全背包问题
        //dp[i]表示凑成总金额i的货币组合数
        int[] dp=new int[amount+1];
        //初始化dp
        dp[0]=1;//因为后台测试数据是默认,amount = 0 的情况,组合数为1的。
        for(int i=0;i

看到这个结论,但我感觉不太对(因为我觉得不是绝对的吧):

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

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

377. 组合总和 Ⅳ

这道题是排列,不是组合了,完全背包问题,这道题让我联想到了爬楼梯那道,因为后面的dp[i]会依赖dp[i-1]

动规五部曲分析如下:

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

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

2.确定递推公式

求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

3.dp数组如何初始化

因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。

4.确定遍历顺序

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历

5.举例来推导dp数组

代码随想录第44天|完全背包,377. 组合总和 Ⅳ,518.零钱兑换II_第2张图片

代码实现

class Solution {
    public int combinationSum4(int[] nums, int target) {
        //完全背包
        //dp[j]含义是组成目标整数为j的方法个数
        //这道题求的是排列
        int[] dp=new int[target+1];
        dp[0]=1;//初始化
          for(int j=1;j<=target;j++){
           for(int i=0;i

 

你可能感兴趣的:(算法训练营,算法)