解释一道动态规划题目的思想

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change

题目

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

示例1
输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1
示例2
输入: coins = [2], amount = 3
输出: -1
说明

你可以认为每种硬币的数量是无限的。

我理解题目的过程
动态规划:尝试分解子问题

- 在研究了好几天,看了大佬们无数的解题思想之后,我终于明白了动态规划的本质,其实理解
  一个算法的思想,有很多时候只差临门一脚,希望我的题解能帮助到大家。
  
- 我们经常听到「最优子结构」「缩小问题规模」「自顶向下」「自底向上」等跟动态规划
  相关的词汇。
  
- 接下来就彻底搞懂这种思想,顺带着我自己也重温一遍刚刚搞懂的喜悦。

----------------开始解题,拿实例来说话----------------------

- 假设给出的不同面额的硬币是[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[119] + 1, dp[118] + 1, dp[115] + 1);
    
  - 推导出「状态转移方程」:
    dp[i] = Math.min(dp[i - coin] + 1, dp[i - coin] + 1, ...)
    其中 coin 有多少种可能,我们就需要比较多少次,那么我们到底需要比较多少次呢?
    当然是 coins 数组中有几种不同面值的硬币,就是多少次了~ 遍历 coins 数组,
    分别去对比即可
    
  - 上面方程中的 dp[119],dp[118],dp[115] 我们继续用这种思想去分解,
    这就是动态规划了,把这种思想,思考问题的方式理解了,这一类型的题目
    问题都不会太大

leetcode题解链接

代码实现
/*
  动态规划
*/
var coinChange = function(coins, amount) {
  let dp = new Array( amount + 1 ).fill( Infinity );
  dp[0] = 0;
  
  for (let i = 1; i <= amount; i++) {
    for (let coin of coins) {
      if (i - coin >= 0) {
        dp[i] = Math.min(dp[i], dp[i - coin] + 1);
      }
    }
  }
  
  return dp[amount] === Infinity ? -1 : dp[amount];
}

/*
  先来一波超时算法:DFS
  第 15 个测试用例就超时了 !!
*/
var coinChange = function(coins, amount) {
  let least = Infinity, len = coins.length;
  
  // backtrack[总金额, 硬币数, 下一个硬币的索引]
  function backtrack(sum, num, index) {
    // 如果总金额超过了 amount ,终止本次递归
    if (sum > amount) return ;
    if (least !== Infinity && num >= least) return ;
    
    // 如果总金额等于 amount,比较最小数量,终止本次递归
    if (sum === amount) {
      return least = Math.min(least, num);
    }
    
    // 继续遍历
    for (let i = index; i < len; i++) {
      sum += coins[i];
      backtrack(sum, num + 1, index);
      sum -= coins[i];
    }
  };
  backtrack(0, 0, 0);
  
  return least === Infinity ? -1 : least;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值