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...n1minF(icj)+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. 如果最后(包含)一枚是1,那么11-1=10 ,我们就需要知道兑换10所需的最少硬币数量dp[10]。
  2. 如果最后(包含)一枚是2,11-2=9,我们就需要知道兑换9所需的最少硬币数量dp[9]。
  3. 如果最后(包含)一枚是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这些金额所需要的硬币最少组合值。

金额01234567891011
最少硬币011221223323

求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]
};

  • 37
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值