Leetcode 518-零钱兑换 II(背包问题总结)

本文介绍了如何使用动态规划解决完全背包问题,包括一维和二维DP数组的应用。通过举例说明了遍历顺序对结果的影响,强调了完全背包中物品循环在外,容量循环在内的原则,并对比了完全背包与01背包的区别。同时,提供了两种不同的DP实现方法,解释了代码逻辑和注意事项。
摘要由CSDN通过智能技术生成

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。
在这里插入图片描述

题解

法一 一维dp

本题为组合问题,物品循环在外,背包容量循环在内。
对于排列问题如爬楼梯问题,物品循环在内,背包容量循环在外。

例如:
输入: amount = 4, coins = [1, 3]

  • 对于物品循环在外 背包容量循环在内,即:
    for (int i = 0; i < coins.length; i++)
    for (int j = 0; j <= amount; j++)

coins[0]=1 dp[3]+=dp[2] 即{1,1,1}
coins[0]=1 dp[4]+=dp[3] 即{1,1,1,1}
coins[0]=3 dp[3]+=dp[0] 即{1,1,1} 和{3}
coins[0]=3 dp[4]+=dp[3] 即{1,1,1,1} 和{3,1}

  • 对于物品循环在内 背包容量循环在外,即:
    for (int j = 0; j <= amount; j++)
    for (int i = 0; i < coins.length; i++)

j=3,coins[0]=1 dp[3]+=dp[2]=1 即{1,1,1}
j=3,coins[1]=3 dp[3]+=dp[0]=2 即{1,1,1}和{3}
j=4,coins[0]=1 dp[4]+=dp[3] 即{1,1,1,1}和{3,1}
j=4,coins[1]=3 dp[4]+=dp[1] {1,1,1,1}和{3,1}和{1,3}

原因:

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

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

爬楼梯问题与零钱兑换区别参考链接1
爬楼梯问题与零钱兑换区别参考链接2

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        //注意因为后续的dp都由dp[0]转化而来,所以dp[0]应该初始化为1,表明使得金额数为0有1种方案,即一个硬币都不取
        dp[0] = 1;
        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];
    }
}

在这里插入图片描述

注意

本题为完全背包,即每个物品可取多次,不同于每个物品只取一次的01背包

  • 对于01背包,一维dp要物品在外,容量在内(二维两者顺序不重要),容量遍历顺序从高到低,
  • 对于完全背包(组合问题),一维dp要物品在外,容量在内(二维两者顺序不重要),容量遍历顺序从低到高
    原因:倒序遍历是为了保证物品i只被放入一次。但如果一旦正序遍历了,那么物品i就可能会被重复加入多次!

倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!

举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

如果正序遍历

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

为什么倒序遍历,就可以保证物品只放入一次呢?

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了

01背包详解参考链接

法二 二维dp

注意:因为是完全背包,一个coins可以取若干次,所以当前coins可以取k次
二维dp可参考题解

class Solution {
    public int change(int amount, int[] coins) {
        //dp[0][j]表示不取任何硬币,dp设置偏移量是为了避免分类讨论(转移方程中有dp[i-1]][j])
        int[][] dp = new int[coins.length+1][amount + 1];
        //注意因为后续的dp都由dp[0]转化而来,所以dp[0]应该初始化为1,表明使得金额数为0有1种方案,即一个硬币都不取
       dp[0][0]=1;      

      for (int i = 1; i <= coins.length; i++) {
          for (int j = 0; j <= amount; j++){
              //k=0表示不取当前硬币,因为是完全背包,所以存在取若干个当前硬币的情况
              for(int k = 0; k*coins[i-1] <= j; k++){
                    dp[i][j] += dp[i-1][j - coins[i-1]*k];
                }
            }
        }
        return dp[coins.length][amount];
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值