题目
题目来源:零钱兑换 - 力扣
分析
这个问题乍一看似乎用贪心算法能够很简单的解决:先拿最大的数去尝试,当最大的数放不下的时候,再尝试较小的数。
对于示例一确实是这样的,但假如情况是下面这样呢?
而如果用贪心算法,则会得出 4 + 1 + 1
的答案,这再一次证明了一点:局部最优的解法叠加起来往往并不会是全局最优解。
显然,这里还是一个动态规划的问题。
相对于三角形最小路径和而言,硬币问题要更加抽象一些,这里以第二个示例为例,来讲解一下应该如何进行推导。
首先我们需要建立一张表:coins.length == 3
,amount == 6
,得出如下表的框架:
想要填满这个表,显然需要通过 i, j 进行遍历,最终可以得到以下结果:
而关键点则在于:当递推到下一层某个结果,上面存在不同解的时候,如何找到更合适的答案 。
这里我们引入 dp 用于存储每一步符合结果的数量,显然它的长度应该是 amount + 1
:
经过推导,可以得出 dp 的公式:
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
由此,我们可以得到如下代码:
var coinChange = function (coins, amount) {
if (amount === 0) return 0;
const dp = Array(amount + 1).fill(Number.MAX_VALUE);
dp[0] = 0;
for (let i = 0; i < dp.length; i++) {
for (let j = 0; j < coins.length; j++) {
const cj = coins[j];
if (i - cj >= 0) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[dp.length - 1] === Number.MAX_VALUE ? -1 : dp[dp.length - 1];
};
结果如下:
💡:动态规划相对来说是比较抽象的问题,其关键点在于如何一步步进行推导,更多的是利用数学的思维进行思考。