『算法』动态规划典中典-最小硬币找零问题

359 阅读3分钟

碎碎念🤥

大家好,我是潘小安,一个永远在减肥路上的前端er🐷 !

六月以来发生很多事,离职,住院,诈骗。。。。这两个月以来经历了人生中一些比较痛苦的阶段,也有一些收获,在年尾回头看的时候,再和大家细细道来。

最近打算重拾算法计划,打算在之前的基础上,为每一道经典的算法题写一篇自己的题解,不会做的题目就把官方的或者我认为好的题解咀嚼之后再输出成自己的语言,废话不多说,上菜

上菜 🥬

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

示例 1:

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

示例 2:

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

思路

  当题目中出现 “最” “极” 等字样时候,我们就可以考虑尝试使用动态规划去解题了,之前的动态规划的文章中提到过,动态规划分三步走,和把大象装进冰箱是一样样的

  • 找初始值
  • 找规律(动态转移方程)
  • 拿结果

 以 demo 中的 coins = [1,2,5],amount=11 举例,我们使用 F(n)F(n) 来表示 amountn 时候,需要的最小硬币数量。

 当 amount0 的时候,所需的硬币数为 0,即 F(0)=0F(0)=0

 当 amout1 的时候,所需硬币数取决于 coins 中所有硬币,

如果 coins 中有面值为 1 的,则最少硬币数为 :

F(1)=Math.min(F(11)F(12)F(15))+1F(1)=Math.min(F(1-1)F(1-2)F(1-5))+1

显然当F(负数)不加入计算

amoutn 的时候,所需最少硬币为

F(i)=minj=0n1F(icj)+1F(i)= \mathop{\min}\limits_{j=0…n−1} F(i−c j )+1

于是就可以得到动态规划中的转移方程,初始值为 F(0)=0F(0)=0,求 F(amount)F(amount) 的值。

image.png

function coinChange(coins, amout) {
  const dp = [];
  // 初始值
  dp[0] = 0;
  for (let curamount = 1; curamount <= amout; curamount++) {
    dp[curamount] = Infinity;
    for (let curcoin = 0; curcoin < coins.length; curcoin++) {
      if (curamount - coins[curcoin] >= 0) {
        // 转移方程
        dp[curamount] = Math.min(
          dp[curamount],
          dp[curamount - coins[curcoin]] + 1
        );
      } 
    }
  }
  return dp[amout] === Infinity ? -1 : dp[amout];
}

写到这里,算法就完成了,在计算curamount - coins[curcoin]的时候,其实可以先对硬币从小到大排序之后,之后只要一次负值就可以直接退出内层循环,新的代码如下:

function coinChange(coins, amout) {
    ...
+  coins.sort((a, b) => {
+    return a - b;
+  });
  for (let curamount = 1; curamount <= amout; curamount++) {
    dp[curamount] = Infinity;
    for (let curcoin = 1; curcoin < coins.length; curcoin++) {
      if (curamount - coins[curcoin] >= 0) {
        // 转移方程
        dp[curamount] = Math.min(
          dp[curamount],
          dp[curamount - coins[curcoin]] + 1
        );
      } 
+      else {
+        break;
+      }
    }
  }
  return dp[amout] === Infinity ? -1 : dp[amout];
}

但其实 sort 本身也有时间损耗,所以当 coins 数组特别大的情况下还是可以做下取舍。

小声BB🤫

真正的英雄主义是认清生活的真相后,仍然热爱生活

随着时间的流逝,年纪的增长,好像越来越开始体会到这句话里面包含的力量与勇气。

愿我们足够幸运,能够远离一些无意义的苦难

愿我们足够坚强,能够挺过一些有意义的苦难

愿我们足够热爱,能够经历一切之后,保持对美好生活的向往