【刷穿leetcode】322. 零钱兑换|刷题打卡

1,277 阅读4分钟

一、题目描述:

leetcode322题:零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

示例 4:

输入:coins = [1], amount = 1
输出:1

示例 5:

输入:coins = [1], amount = 2
输出:2

二、思路分析:

解法1:贪心算法

由题意可知,想要凑成总金额使用的硬币个数最少,我们肯定优先选择最大面额的硬币。因而可以采用贪心算法来解决。

  1. 硬币降序排列,从0到数组长度拿硬币,优先用大额硬币兑换
  2. amount / 硬币面值 取整 = 可用当前面值的硬币最多数量
  3. amount = 0,一次兑换结束,更新最小兑换数量
  4. 剪枝:记录兑换数量,后续兑换数量不能大于最小兑换数量

解法2:动态规划

该题还可以采用自底而上的动态规划来解决。
假设给出的不同面额的硬币是[1, 2, 5],目标是 120,问最少需要的硬币个数?

  • 我们要分解子问题,分层级找最优子结构,看到这又要晕了哈,憋急~~ 下面马上举例。

  • 这里我们使用「自顶向下」思想来考虑这个题目,然后用「自底向上」的方法来解题, 体验算法的冰火两重天。

  • dp[i]: 表示总金额为 i 的时候最优解法的硬币数

  • 我们想一下:求总金额 120 有几种方法?下面这个思路关键了 !!! 一共有 3 种方式,因为我们有 3 种不同面值的硬币。

    1. 拿一枚面值为 1 的硬币 + 总金额为 119 的最优解法的硬币数量。这里我们只需要假设总金额 为 119 的最优解法的硬币数有人已经帮我们算好了,不需要纠结于此。即:dp[119] + 1
    2. 拿一枚面值为 2 的硬币 + 总金额为 118 的最优解法的硬币数。这里我们只需要假设总金额为 118 的最优解法的硬币数有人已经帮我们算好了,即:dp[118] + 1
    3. 拿一枚面值为 5 的硬币 + 总金额为 115 的最优解法的硬币数。这里我们只需要假设总金额为 115 的最优解法的硬币数有人已经帮我们算好了,即:dp[115] + 1
  • 所以,总金额为 120 的最优解法就是上面这三种解法中最优的一种,也就是硬币数最少 的一种,我们下面试着用代码来表示一下:

  • dp[120] = Math.min(dp[120], dp[119] + 1, dp[118] + 1, dp[115] + 1);

  • 推导出「dp方程」: dp[n] = Math.min(dp[n], dp[n - coin] + 1) 其中 coin 为硬币的面值,当然是 coins 数组中有几种不同面值的硬币,就是多少次了~ 遍历 coins 数组,分别去对比即可。

三、AC 代码:

贪心算法的代码

var coinChange = function(coins, amount, res = Infinity) {
    coins.sort((a, b) => b - a)
    var d = (amount, index, count) => {
        if (amount === 0) return res = Math.min(res, count)
        if (index === coins.length) return
        for (var n = amount / coins[index] | 0; count + n < res && n >= 0; n--) 
            d(amount - n * coins[index], index + 1, count + n)
    }
    return d(amount, 0, 0), res === Infinity ? -1 : res
};

动态规划的代码

var coinChange = function(coins, amount, dp = [0].concat(Array(amount).fill(amount + 1))) {
  for (var i = 1; i <= amount; i++)
      for (coin of coins)
          if (i >= coin) dp[i] = Math.min(dp[i], dp[i - coin] + 1)
  return dp[amount] > amount ? -1 : dp[amount]
};

四、总结:

通过做零钱兑换这道题,首先温习了上周学习的动态规划,然后又学习了贪心算法的解题思想。贪心算法适合:在对问题求解时,总是做出当前最好的选择。零钱问题就是要优先选择最大面值的硬币匹配,因此可以采用贪心算法求解。

本文正在参与「掘金 3 月闯关活动」,点击查看活动详情