[路飞]_Leetcode 322. 零钱兑换

54 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

今天我们来做一下leetcode上面的一道比较经典的题目 322. 零钱兑换

题意

image.png 先看题目,题目叙述比较简单,给了我们一个几种面额的硬币无限枚和一个总金额,让我们求使用这些硬币凑成总金额的最少硬币个数。

看到这里,如果是了解动态规划的同学可能会想到这道题会使用到,但是没有接触过的可能就很纳闷了。

思路

好了,废话不多说,直接开始分析。

既然这道题涉及到动态规划,那我们首先要想到的就是题目种隐藏的递推关系和状态转移的过程。

如果拿示例1来看,你可能本能的想到先拿最大的面额,然后最大的超过余额之后,在尝试使用小面额,当然这也是一种搜索方法,自顶向下也可以解决到这个题目。

我们也可以从问题规模入手,先尝试简答的情况,再进一步进行推算。 比如在示例1中 coins=[1,2,5] amount=11

  • 1.如果需要凑成的总金额是 1,能使用的硬币有 1 , 最小的个数当然是1
  • 2.如果需要凑成的总金额是 2,能使用的硬币有 1 2,现在有两个选择,选择1还是选择2
    • 如果我们使用1,这时最小的兑换次数应该等于 余额1的最小个数 + 选择硬币1的个数1,而由第一步可以知道1的最小兑换个数是1,所以我们得出如果现在使用1时最小的兑换个数是2.
    • 同理,如果选择2,那么最小的兑换次数等于 余额0的兑换个数 + 选择硬币2的个数1,而余额如果是0,就不需要兑换了硬币,也就是兑换个数为0,所以,选择2时的最小兑换次数是1。那么,最终,我们在这两种选择方法中选择最小的一个作为答案,我们就得出了,凑成金额2的最小兑换次数是1
  • 3.如果要凑金额3,能使用的硬币有 1 2 3,现在有三个选择,拿硬币1、硬币2还是硬币3
    • 和2一样,三种情况分别讨论,如果选择1,剩余金额2,次数的最小兑换个数 = 剩余金额2的兑换个数 + 1,由上面2可以知道 金额2的兑换最小个数为1,所以就得到了此时的兑换个数 = 1 + 1 = 2
    • 如果选择2,剩余金额1,次数的最小兑换个数 = 剩余金额1的兑换个数 + 1,由上面1可以知道 金额1的兑换最小个数为1,所以就得到了此时的兑换个数 = 1 + 1 = 2
    • 如果选择3,剩余金额0,次数的最小兑换个数 = 剩余金额0的兑换个数 + 1,金额0不需要兑换,也就是兑换个数为0,所以就得到了此时的兑换个数 = 0 + 1 = 2
    • 综上,三种情况取最小值,最终为min(2,2,1)=1
  • 4.如果凑成金额4,分析过程与上面类似。我们持续这样的递推过程,分析当前金额的最小兑换次数时,都可以从之前计算出的前一个结果中,获取到数目,从而计算出更大金额的最小兑换个数,这样的过程也就是状态转移了,转移过程如下表所示。
硬币面额\兑换金额1234567891011
112233233443
2--1223323344
5--------1223323

这个表格中的数字有什么含义呢? 假设其中加粗的2,代表含义就是在我们从小到大凑金额的过程中,要凑金额4,当前如果拿了面值2的硬币的话,最小的兑换个数 = 2

表格代表我们的递推方向是 从左到右从上到下的,也就是我们从小金额开始凑起,每次按照硬币列表的顺序,一次拿到对应的硬币,然后返回拿到某个硬币之后最小的兑换次数,并保存下来当前的状态,便于后面计算时会使用到

分析完整个递推过程之后,就可以开始编写代码了,当然,也是按照三步进行

  • 状态定义 dp[i]凑成金额i的最小兑换次数
  • 状态初始化 dp[0]=0 金额0不需要兑换,也就是兑换个数是0
  • 递推方程 dp[i] = min(dp[i- coin1] + dp[i-coins2] + ... + dp[i-coins(n-1)]) + 1

代码实现

image.png

结束语

如果有更好的分析思路,欢迎大家在评论区发表看法!⛄