零钱兑换 JS实现
题目描述(LeetCode)
先看下官方描述:
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
动态规划
我们采用自下而上的方式进行思考。仍定义 F(i)为组成金额 i所需最少的硬币数量,假设在计算 F(i)之前,我们已经计算出 F(0)-F(i-1)的答案。 则 F(i)对应的转移方程应为
F ( i ) = min j = 0... n − 1 F ( i − c j ) + 1 F(i)= \min_{\mathclap{j=0...n-1}} F(i - c_{j})+1 F(i)=j=0...n−1minF(i−cj)+1
其中 c j c_j cj代表的是第 j枚硬币的面值,即我们枚举最后一枚硬币面额是 c j c_j cj,那么需要从 i- c j c_j cj这个金额的状态 F(i- c j c_j cj) 转移过来,再算上枚举的这枚硬币数量 1的贡献,由于要硬币数量最少,所以 F(i)为前面能转移过来的状态的最小值加上枚举的硬币数量 1 。
上面是官方给的状态转移方程,我估计很多小伙伴最初看到这里的时候会有点懵,咱们先先来分析下是如何将问题分步求解的,也就是说上面的方程是怎么推导出来的,这是解题的关键。
参照示例1的题目,兑换11这个金额,目前有1,2,5三种硬币,先倒着推导,可以这么理解:假设凑齐11这个金额所需的最后一枚硬币是1,2,5其中的一个(也可以理解为兑换11所需要的硬币其中可能包含1,2,5中的一个),我们就分别看看包含哪个硬币的情况下,剩余金额使用的硬币数量最少,那么就会出现以下三种情况:
- 如果最后(包含)一枚是1,那么11-1=10 ,我们就需要知道兑换10所需的最少硬币数量dp[10]。
- 如果最后(包含)一枚是2,11-2=9,我们就需要知道兑换9所需的最少硬币数量dp[9]。
- 如果最后(包含)一枚是5,11-5=6,我们就需要知道兑换6所需的最少硬币数量dp[6]。
然后我们取出dp[10],dp[9],dp[6]三个值中的最小值,加上我们刚才拿出的那一枚硬币,就得出了兑换11的答案,这就是状态转移的过程。
那么问题来了,dp[10],dp[9],dp[6]这些值是怎么得到的呢?
其实就是重复上面的步骤,例如我们要知道dp[10]的值,也是分三种情况:10-1=9,10-2 = 8, 10-5= 5,以此类推。。。
这样如果知道了dp[0-10]的状态值,问题就解决了。我们采用自下而上的方式依次记录兑换0至11这些金额所需要的硬币最少组合值。
金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
最少硬币 | 0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 |
求dp[1]的时候,很简单,结果肯定是1。
求dp[2]的时候,分别计算刚才提高的三种情况(1,2,5三种硬币),分别求2-1=1,2-2=0,2-5=-3(比当前金额大的硬币可以忽略)即dp[1],dp[0]中的最小值,然后+1就是dp[2]的结果。同理,dp[3],dp[4]…dp[11]都可以求出来了。
接下来,就看代码怎么写了,
第一步:如果需要记录这12个数,我们就需要一个长度为12的数组来记录每一个值的状态。
第二步:处理标记0-amount金额的状态,遍历coins里的所有硬币去计算出最少硬币数。
第三步:返回状态值,处理无法兑换的情况。
function coinChange(coins, amount){
//初始化一个长度为12,用来存储状态的数组,默认值填充为amount+1,也可以填充为最大值,用来后续比较。
let dp = new Array(amount + 1).fill(amount + 1)
//设置初始0的最少硬币为0
dp[0] = 0
//分别处理标记0-amount的状态值
for (let i = 0; i < dp.length; i++) {
//用不同的硬币种类去判断各种情况
for (let j = 0; j < coins.length; j++) {
//如果当前金额大于当前判断的硬币
if (i - coins[j] >= 0) {
//dp[i-coins[j]]获得该价格下的最优数量
dp[i] = Math.min(dp[i], 1 + dp[i - coins[j]])
}
}
}
//是否初始化的值,如果是初始值说明没有兑换成功
return (dp[amount] === amount + 1) ? -1 : dp[amount]
};