518-零钱兑换 II(完全背包)

该博客介绍了一种动态规划的解决方案,用于计算给定不同面额硬币和总金额时,能够凑成总金额的硬币组合数。示例中展示了如何用Java实现这个算法,包括边界条件和递推公式。这是一个典型的完全背包问题,适用于求解组合计数。

题目

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 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

示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

示例 3:

输入: amount = 10, coins = [10]
输出: 1

题解

class Solution {
    public int change(int amount, int[] coins) {
        if(coins==null){
            return 0;
        }
        int[] dp=new int[amount+1];
        dp[0]=1;// 有一种方案凑成 0 元,那就是 一个也不选
        for(int coin:coins){
            // 记录每添加一种面额的零钱,总金额j的变化
            for(int i=coin;i<=amount;i++){
                dp[i]+=dp[i-coin];
            }
        }
        return dp[amount];
    }
}

思路:

  1. 转化为是否可以用 coins 中的数组合和成 amount,完全背包问题,并且为“不考虑排列顺序的完全背包问题”,外层循环为选择池 coins,内层循环为 amount。
    dp[i] 表示和为 i 的 coin 组合有 dp[i] 种。

    外层遍历 coins 每个 coin;
    内层遍历 amount。
    对于元素之和等于 i - coin 的每一种组合,在最后添加 coin 之后即可得到一个元素之和等于 i 的组合,因此在计算 dp[i] 时,应该计算所有的 dp[i − coin] 之和。
    dp[i] = dp[i] + dp[i - coin]
    对于边界条件,我们定义 dp[0] = 1,表示只有当不选取任何元素时,元素之和才为 0,因此只有 1 种方案。
    最后返回 dp[amount]。

<think>嗯,用户想了解零钱兑换II动态规划解法。首先,我得回忆一下这个题目的具体要求。题目是说,给定不同面额的硬币和一个总金额,计算可以凑成总金额的组合数,每种硬币可以用无限次。这应该是一个完全背包问题,对吧? 根据引用的资料,比如引用[2]提到这是一个完全背包的方案数问题,需要装满背包。所以动态规划的状态定义应该是dp[i]表示凑出金额i的组合数。初始化的时候,dp[0]应该为1,因为凑出0元只有一种方式,就是不用任何硬币。这一点在引用[4]的代码里也看到,dp[0] = 1。 然后,状态转移方程需要考虑如何累加不同的硬币组合。这里要注意的是,题目要求的是组合数而不是排列数,也就是说,不同的顺序算同一种组合。比如用1和2凑成3,应该是1+2,而不是2+1算两种。所以遍历顺序很重要,必须先遍历硬币,再遍历金额,避免重复计算排列。引用[4]中的代码确实是先遍历硬币,再遍历背包容量,这样确保每个硬币被考虑的顺序固定,不会产生重复的组合。 举个例子,如果硬币是[1,2],金额是3。正确的组合应该是1+1+1,1+2。如果先遍历金额的话,可能会导致计算成1+2和2+1两种,但实际上应该算一种。因此,遍历顺序必须正确,先处理每个硬币,再处理每个可能的金额,这样每个硬币的使用顺序是固定的,不会出现排列的情况。 接下来,具体到状态转移方程,对于每个硬币coin,从coin到amount遍历,dp[j] += dp[j - coin]。这是因为对于当前金额j,可以通过加上当前硬币coin来构成,所以组合数等于之前j - coin的组合数加上当前的组合数。这个过程需要逐个硬币处理,确保组合的唯一性。 然后,代码实现方面,初始化数组dp的长度是amount +1,因为要考虑0到amount的所有金额。然后双重循环,外层是硬币,内层是金额,从当前硬币的面值开始,直到amount。每次更新dp[j]的值。 例如,coins = [1,2,5], amount = 5的情况,初始化dp[0]=1。处理硬币1时,每个金额j从1到5,dp[j] += dp[j-1],所以每个j都变成1,因为只有一种方式(全用1)。接着处理硬币2,从j=2开始,dp[2] += dp[0],即1,所以现在dp[2]=2(1+1和2)。继续到j=3,dp[3] += dp[1]=1,变成2,依此类推。这样一步步累加,最后得到正确的组合数。 用户可能会有的疑问包括为什么外层是硬币而不是金额,这时候需要解释组合和排列的区别,以及如何避免重复计算。此外,可能还需要考虑如果硬币有0面额或者amount为0的特殊情况处理,但题目中可能已经排除了这些情况。 另外,还需要注意时间复杂度,因为双重循环的时间是O(len(coins)*amount),这在题目给定的范围内应该是可行的。空间优化方面,这里使用了一维数组,每次覆盖更新,节省了空间。 总结起来,这个问题的核心在于理解完全背包的组合数问题,正确处理遍历顺序以避免排列,以及正确应用动态规划的状态转移方程。代码实现相对直接,但需要确保循环的顺序正确。</think>### 零钱兑换 II 动态规划详解 #### 问题描述 给定一组不同面额的硬币 `coins` 和一个总金额 `amount`,计算**组合方式的数量**,使得这些硬币的总金额等于 `amount`。每个硬币可以无限次使用。 #### 关键思路 1. **完全背包问题**:将硬币视为物品,金额视为背包容量,转化为“恰好装满背包的组合数”问题[^2]。 2. **动态规划定义**: - `dp[j]` 表示凑出金额 `j` 的组合数。 - 初始化 `dp[0] = 1`(凑出0元只有一种方式:不选任何硬币)[^4]。 3. **遍历顺序**: - **外层遍历硬币**:确保组合不考虑顺序(例如 `1+2` 和 `2+1` 视为同一种组合)。 - **内层遍历金额**:从当前硬币面值开始,逐步更新 `dp[j]`[^4]。 #### 状态转移方程 $$ dp[j] = \sum_{i=0}^{n-1} dp[j - \text{coins}[i]] \quad (\text{当 } j \geq \text{coins}[i]) $$ 实际通过循环累加实现: ```python for coin in coins: for j in range(coin, amount + 1): dp[j] += dp[j - coin] ``` #### 代码实现 ```python def change(amount: int, coins: list[int]) -> int: dp = [0] * (amount + 1) dp[0] = 1 # 初始化:金额0有1种组合方式(不选任何硬币) for coin in coins: for j in range(coin, amount + 1): dp[j] += dp[j - coin] return dp[amount] ``` #### 示例分析 假设 `coins = [1, 2, 5]`, `amount = 5`: 1. **初始化**:`dp = [1, 0, 0, 0, 0, 0]` 2. **处理硬币1**: - `dp[1] += dp[0]` → `dp[1] = 1` - `dp[2] += dp[1]` → `dp[2] = 1` - ... 最终所有金额均被更新为1(全用1元硬币) 3. **处理硬币2**: - `dp[2] += dp[0]` → `dp[2] = 2`(组合方式:`1+1` 和 `2`) - `dp[3] += dp[1]` → `dp[3] = 2`(`1+1+1` 和 `1+2`) 4. **处理硬币5**: - `dp[5] += dp[0]` → `dp[5] = 3`(新增组合 `5`) 最终结果 `dp[5] = 4`,即组合方式:`[1,1,1,1,1]`, `[1,1,1,2]`, `[1,2,2]`, `[5]`。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codrab

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值