[LeetCode:Coin Change] | 刷题打卡

351 阅读3分钟

真心求大家关注和点赞,原创不易,你们的支持是我写作的最大动力!

题目描述

给你不同面值金币的数组 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

按照上面的算法:

  1. 第一次肯定选 9 了,然后剩下 amount === 6
  2. 第二次只能选 5 了,然后剩下 amount === 1
  3. 第三次没得选,所以返回 -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):

  1. 如果 k 在 conis 中直接能找到,则 f(k) === 1
  2. 否则:在 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 春招闯关活动」, 点击查看 活动详情