真心求大家关注和点赞,原创不易,你们的支持是我写作的最大动力!
题目描述
给你不同面值金币的数组 coins 和一个总钱数 amount,返回能组成 amount 的最小金币个数,如果不能,返回 -1,每种面值的金币数量无限
例一:
输入: coins = [1,2,5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
例二:
输入: coins = [2], amount = 3
输出: -1
例三:
输入: coins = [1], amount = 0
输出: 0
例四:
输入: coins = [1], amount = 1
输出: 1
例五:
输入: coins = [1], amount = 2
输出: 2
思路分析
这道题是 LeetCode March Challange Week2 第四天的题(果然后面的题是会比前面的难点),考察的是动态规划算法。做这道题的时候问了我学会计的女朋友的一些想法,她思索片刻后告诉我,可以先尽可能的兑换大面值,然后再兑换第二大的面值,然后第三大...直到最后 amount 归零或者无法兑换为止。
这种方案我相信包括我在内的大部分同学肯定都想过,当时骑车下班回家,吹着冷风想着反例,饿着肚子想吃板栗,幸好没被车撞...反例也想到了。coins = [5,9],amount = 15
按照上面的算法:
- 第一次肯定选 9 了,然后剩下 amount === 6
- 第二次只能选 5 了,然后剩下 amount === 1
- 第三次没得选,所以返回 -1,但其实正确答案是 3(5+5+5)
回到第一个例子:coins = [1,2,5], amount = 11,我们要(第四声)求 amount = 11 时,最少的金币个数。定义 f(x) 表示 amount 为 x 时,需要的最少的金币个数,那么我们有:
- f(1) = 1
- f(2) = 1
- f(5) = 1
显而易见,兑换 1 块、2 块和 5 块最少只需要一枚金币
目标是求:f(11)
- 假如我们知道了 f(10) 呢?f(11) 可以用 f(10) + f(1) 表示
- 假如我们知道了 f(9) 呢?f(11) 可以用 f(9) + f(2) 表示
- 假如我们知道了 f(6) 呢?f(11) 可以用 f(6) + f(5) 表示
而 f(1)、f(2) 和 f(5) 等于 1,所以 f(11) 的最小值就一定是:Min(f(10),f(9),f(6)) + 1
那么 f(10)、f(9) 和 f(6) 我们也可以用同样的方法推出来,在推的时候一定是基于当前已经计算过的值,这是一个自顶向下的过程。
我们将上述过程抽象成一般过程,求 f(k):
- 如果 k 在 conis 中直接能找到,则 f(k) === 1
- 否则:在 coins 中找到所有比 k 小的面值(例如:ij),找出 f(k-i)、f(k-j) 的最小值,最小值 + 1 即 f(k) 的值
写出的动态规划代码如下:
AC 代码
var coinChange = function (coins, amount) {
if (amount === 0) {
return 0;
}
const set = new Set(coins);
const dp = [];
for (let i = 1; i <= amount; i++) {
if (set.has(i)) {
dp[i] = 1;
} else {
let min;
for (let coin of coins) {
if (coin < i && dp[i - coin]) {
if (min === undefined || dp[i - coin] < min) {
min = dp[i - coin] + 1;
}
}
}
dp[i] = min;
}
}
return dp[amount] || -1;
};
总结
一般来说,求最值的问题会用到动态规划算法,相比于暴力穷举所有可能算最值,动态规划能很好的利用已求解的子问题,计算最终结果,自底向上一步步逼近最终结果。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情