一起刷LeetCode——零钱兑换(动态规划)

38 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情

零钱兑换

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

来源:力扣(LeetCode) 链接:leetcode.cn/problems/co…

分析

  • 要找到凑成金额需要的最少的硬币个数,那只需要把能凑出来总金额的方法都列举出来,其中就能找到最少的硬币个数,对列举方法进行优化:如果把最少硬币个数的这种凑法当做最优子结构,再有一个状态转移方程,这道题目可以通过动态规划来解决
  • 状态维护最小硬币个数,能凑成当前金额n的最小硬币个数dp[n],等于在遍历不同面额的硬币时,当前硬币面值问m,dp[n-m]+1的最小值

递归

  • 使用递归循环状态,关注每个子问题,通过递归子问题得出结果
代码
/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var memo = {}
var coinChange = function(coins, amount) {
  if (amount === 0) return 0
  if (amount < 0) return -1
  if (memo[amount] !== undefined) return memo[amount]
  let fewest = Number.MAX_SAFE_INTEGER
  for (let c of coins) {
    let sub = coinChange(coins, amount - c)
    if (sub === -1) continue
    fewest = Math.min(fewest, sub + 1)
  }
  memo[amount] = fewest === Number.MAX_SAFE_INTEGER ? -1 : fewest
  return memo[amount]
}

数组迭代

  • dp数组定义为总金额n最少需要dp[n]个硬币,结合常规动态规划中的关于最优子结构部分的分析,通过数组迭代,dp[n]的值就是要求的值
代码
/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function(coins, amount) {
  let dp = Array(amount + 1).fill(amount + 1);
  dp[0] = 0;
  for (let a = 1; a < amount + 1; a++) {
    for (let c of coins) {
      if (a - c >= 0) {
        dp[a] = Math.min(dp[a], 1 + dp[a - c]);
      }
    }
  }
  if (dp[amount] !== amount + 1) {
    return dp[amount];
  } else {
    return -1;
  }
};

总结

  • 本题的难度等级为中等
  • 在得到状态转移方程之后,可以选择递归和跌打,递归遍历的模型像是树结构,迭代的模型是数组/链表
  • 今天也是有收获的一天