LeetCode518. 零钱兑换 II

344 阅读3分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

题目

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

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

注意,你可以假设:

  • 0 <= amount (总金额) <= 5000
  • 1 <= coin (硬币面额) <= 5000
  • 硬币种类不超过 500 种
  • 结果符合 32 位符号整数

解题思路

题目可以转化为完全背包问题,如: 一个背包容量为amount和n个物品coins,每个物品的重量为coins[i],每个物品的数量无限,问最多存在几种能够恰好将背包装满的装法?

n为coins的长度

1. dp数组定义

dp[i][j] = x, 表示对于前i个物品,当背包容量为j时,此时最多有x种装法 即 只使用coins中的前i个硬币的面值,想凑出金额j,有x种凑法

2. 选择 和 状态

选择: 装进背包 或 不装进背包

状态: 背包的容量 和 可选择的物品(有两个状态,所以dp用一个二维数组)

状态转移

如果把第i个物品装入背包(即使用coins[i]这个面值的硬币),此时凑法dp[i][j]=dp[i][j-coins[i-1]] ;j-coins[i-1]表示当前背包的容量j减去当前i的重量coins[i-1]; (因为i是从 1 开始的,所以coins的索引为i-1时表示第i个硬币的面值) 如果不把第i个物品装入背包(即不使用coins[i]这个面值的硬币),此时凑法为dp[i][j]= dp[i-1][j],表示和之前状态的结果一样

3. base case dp[0][..] = 0 如果不使用任何硬币面值,就无法凑出任何金额,即0种凑法 和 dp[..][0] = 1 (dp[0][0]=1) 如果要凑出的目标金额为 0,那么有唯一的一种凑法

function change(amount, coins) {
    let n = coins.length;
    // 定义并初始化二维数组
    const dp = [];
    for (let i = 0; i < n + 1; i++) {
        dp[i] = [];
        for (let j = 0; j < amount + 1; j++) {
            // base case
            if (i === 0) {
                dp[0][j] = 0;
            }
            // base case
            if (j === 0) {
                dp[i][0] = 1;
            } else {
                dp[i][j] = 0;
            }
        }
    }

    console.log(dp);
    
    // 做选择
    for (let i = 1; i < n + 1; i++) {
        for (let j = 1; j < amount + 1; j++) {
            // 当选择的第i个硬币的金额比想凑的金额大时,即只有选择不装
            if (j - coins[i - 1] < 0) {
                dp[i][j] = dp[i - 1][j];
            } else {
                // dp[i][j] = 不装 + 装
                dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
            }
        }
    }
    // 所求的答案
    return dp[n][amount];
}