518. 零钱兑换 II(完全背包问题)

题目链接
在这里插入图片描述

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        // 完全背包问题
        // 组合数问题,不需考虑顺序
        // 凑成总金额j的货币组合数为dp[j]
        // dp[j] += dp[j - coins[i]]

        vector<int> dp(amount + 1, 0);
        // 凑成0有一种组合
        dp[0] = 1;
        for (int i = 0; i < coins.size(); i ++) {
            for (int j = coins[i]; j <= amount; j++) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];

    }
};

总结

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

  • 我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况,是组合;如果把两个for交换顺序,是排列。

<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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值