LeetCode——518. 零钱兑换 II

164 阅读2分钟

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

518. 零钱兑换 II

题目

难度:中等

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

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

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

题目数据保证结果符合 32 位带符号整数。

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

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

方法:动态规划

解题思路

完全背包问题

设二维数组dp[i][j]表示前i(i从1开始计数)种硬币可以组成金额j的组合数,对于组成金额j的最后一枚硬币是否使用coin = coins[i - 1]进行讨论

  1. coin > j,即第i种硬币面值超过总金额j,则不使用该硬币,有dp[i][j] = dp[i - 1][j]

  2. coin <= j,即第i硬币面值不超过总金额j

    • 若不使用该硬币,即组合数与dp[i - 1][j]相同

    • 若使用该硬币,则组合数:dp[i][j - coin]

      dp[i][j - coin]:因为coin可以使用无限次,所以最后一枚硬币使用coin,前面也可以使用coin,故组合数为前i种硬币组成金额为j - coin的组合数

    所以dp[i][j] = dp[i - 1][j] + dp[i][j - coin]

初始化,dp[i][0] = 1,即不管使用任何硬币组成面额为0的组合数为1

优化

考虑到dp每一行的计算只与上一行同列以及该行靠前(j - coin)的元素有关,所以不必使用二维数组,可以使用滚动数组,去掉dp的第一个维度

内存循环应从j最小值开始正序遍历,保证转移来的是dp[i - 1][j]不会先被覆盖

代码

/**
 * @param {number} amount
 * @param {number[]} coins
 * @return {number}
 */
var change = function(amount, coins) {
    const dp = new Array(amount + 1).fill(0)
    dp[0] = 1
    for (let coin of coins) {
        for (let j = coin; j <= amount; j++) {
            dp[j] += dp[j - coin]
        }
    }
    return dp[amount]
};

另外,不会出现重复的组合是因为,总是先遍历完一种硬币,再去遍历下一种硬币,即硬币的顺序不会被打乱

算法复杂度分析

  1. 时间复杂度:O(n * amount),n为硬币种类
  2. 空间复杂度:O(amount)