【Daily Interview】- 26 零钱兑换

286 阅读2分钟

题目

img01

题目来源:零钱兑换 - 力扣

分析

这个问题乍一看似乎用贪心算法能够很简单的解决:先拿最大的数去尝试,当最大的数放不下的时候,再尝试较小的数

对于示例一确实是这样的,但假如情况是下面这样呢?

img02

而如果用贪心算法,则会得出 4 + 1 + 1 的答案,这再一次证明了一点:局部最优的解法叠加起来往往并不会是全局最优解。

显然,这里还是一个动态规划的问题。

相对于三角形最小路径和而言,硬币问题要更加抽象一些,这里以第二个示例为例,来讲解一下应该如何进行推导。

首先我们需要建立一张表:coins.length == 3amount == 6,得出如下表的框架:

img03

想要填满这个表,显然需要通过 i, j 进行遍历,最终可以得到以下结果:

img04

而关键点则在于:当递推到下一层某个结果,上面存在不同解的时候,如何找到更合适的答案 。

这里我们引入 dp 用于存储每一步符合结果的数量,显然它的长度应该是 amount + 1

img05

经过推导,可以得出 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];
};

结果如下:

img06

💡:动态规划相对来说是比较抽象的问题,其关键点在于如何一步步进行推导,更多的是利用数学的思维进行思考。