LeetCode热题(JS版) - 322. 零钱兑换

108 阅读2分钟

题目描述

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,则返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

解法分析

这道题可以使用动态规划来解决。我们定义一个长度为 amount+1amount+1 的数组 dpdp,其中 dp[i]dp[i] 表示凑够金额 ii 需要的最少硬币数量。

对于每一个 dp[i]dp[i],它的值都可以由之前的状态转移而来。具体地,对于硬币集合中的每一个硬币 coincoin,我们可以考虑从 dp[icoin]dp[i-coin] 转移而来。因此,我们可以得到以下的状态转移方程:

dp[i]=mincoincoins(dp[icoin])+1dp[i] = \min\limits_{coin \in coins}(dp[i-coin])+1

其中 mincoincoins(dp[icoin])\min\limits_{coin \in coins}(dp[i-coin]) 表示当前金额 ii 可以由硬币集合中的某一枚硬币凑出,因此需要在所有可能的硬币中选取一个最小值,并且由于加上这个硬币需要增加一枚硬币,所以还需再加1。

需要注意的是,对于初始状态 dp[0]dp[0],我们定义它为0。另外,如果无法凑成给定的金额,则 dp[amount]dp[amount] 的值将会保持为初始值 1-1

代码实现

下面给出基于以上思路的JavaScript代码实现,注意数组下标从1开始,同时要处理初始化和特殊情况的边界情况。

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function(coins, amount) {
    const dp = new Array(amount + 1).fill(Number.MAX_SAFE_INTEGER);
    dp[0] = 0;
    for (let i = 1; i <= amount; i++) {
        for (const coin of coins) {
            if (i >= coin) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    return dp[amount] === Number.MAX_SAFE_INTEGER ? -1 : dp[amount];
};

image.png

复杂度分析

时间复杂度

O(nm)O(nm),其中 nn 是硬币面额的数量,mm 是目标金额。算法中通过遍历硬币和金额来计算动态规划数组 dpdp 的值。

空间复杂度

O(m)O(m),其中 mm 是目标金额。算法中使用一个长度为 amount+1amount+1 的数组 dpdp 来存储所有状态。

总结

综上所述,本题通过动态规划思想的处理方式来计算最小的硬币数,时间复杂度和空间复杂度分别为 O(nm)O(nm)O(m)O(m)