Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
一、题目描述:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
输入: coins = [1, 5, 10], amount = 15
输出: 3
二、思路分析:
根据生活常识我们知道人民币的面值是 [1, 5, 10, 20, 50, 100] 。需要凑出某个金额,需要用尽量少的钞票,很显然的策略是能用大的尽量用大的。
var coinChange = function(coins, amount) {
const RMB = coins.sort((a, b) => b - a)
// 先排序钞票,大的在前面小的在后面,能用大的尽量用大的
let count = 0
let current = amount
for (let i = 0; i < RMB.length; i++) {
let use = Math.floor(current / RMB[i])
count += use
current -= RMB[i] * use
}
return count
}
const RMB = [100, 50, 20, 10, 5, 1]
console.log(coinChange(RMB, 666)) // 10
这种算法我们称为贪心算法,特点就是鼠目寸光,能大则大。但是,如果我们换一组钞票的面值,贪心策略就也许不成立了。如果一个奇葩国家的钞票面额分别是 [1, 5, 11],那么我们在凑出15的时候,贪心策略会出错:
15=1×11+4×1 (贪心策略使用了5张钞票)
15=3×5 (正确的策略,只用3张钞票)
代码好好的,到我这里就运行不了了,原来是算法出错了,贪心算法没有考虑全局最有解,而是局部最优解。接下来来分析全局最优解如何计算。
已知面值 [1, 5, 11] ,凑出n需要多少张钞票。假设计算f(n)之前我们以及计算了f(0)到f(n-1)的答案,也就是已经知道凑14需要多少张,凑13需要多少张等等。
让我们从规模最小的n开始。
当n=0时,即我们需要多少个币来凑够0元呢? 由于1,5,11都大于0,即没有比0小的币值,因此凑够0元我们最少需要0个币。于是我们就得到了dp[0]=0, 表示凑够0元最小需要0个硬币。
当n=1时,只有面值为1元的硬币可用, 因此我们拿起一个面值为1的币值,接下来只需要凑够0元即可,而这个是已经知道的dp[0]=0。所以,dp[1]=dp[1-1]+1=dp[0]+1=0+1=1
当n=2时, 仍然只有面值为1的硬币可用,于是我拿起一个面值为1的硬币, 接下来我只需要再凑够2-1=1元即可(记得要用最小的硬币数量),而这个答案也已经知道了。 所以dp[2]=dp[2-1]+1=dp[1]+1=1+1=2。
...
当n=5时,只有面值为5元的硬币可用,因此我们拿起一个面值为5的币值,接下来只需要凑够0元即可,而这个是已经知道的dp[0]=0。所以,dp[5]=dp[5-5]+1=dp[0]+1=0+1=1
当n=6时,我们能用的硬币就有两种了:1元的和5元的( 10元的仍然没用,面值太大了)。 既然能用的硬币有两种,我就有两种方案。如果我拿了一个1元的硬币,我的目标就变为了: 凑够6-1=5元需要的最少硬币数量。即dp[6]=dp[6-1]+1=dp[5]+1=1+1=2。第二种方案是我拿起一个5元的硬币, 我的目标就变成:凑够6-5=1元需要的最少张纸币。即dp[6]=dp[6-5]+1=dp[1]+1=1+1=2。
当n=6时,我们需要计算
Max.min(dp[6-1],dp[6-5],dp[6-11]) + 1
= Max.min(dp[5], dp[1], 0) + 1
= Max.min(1, 1) + 1
= 2
dp[6-11]表示凑6元的时候11元的仍然没用,面值太大了。
function coinChange (coins, amount) {
const Max = amount + 1
const dp = new Array(amount + 1)
dp.fill(Max)
dp[0] = 0
for (let i = 1; i <= amount; i++) {
for (let j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1)
}
}
}
return dp[amount] > amount ? -1 : dp[amount]
}
console.log(coinChange([1, 5, 11], 15)) // 3种
四、总结:
动态规划(dynamic programming,DP)是求解决策过程(decision process)最优化的数学方法。实现的过程会相对复杂。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。