Skip to content

Commit 3c03a29

Browse files
author
lucifer
committed
feat: 增加图片
1 parent a97d4e4 commit 3c03a29

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed

problems/322.coin-change.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ https://leetcode-cn.com/problems/coin-change/
6262

6363
## 思路
6464

65-
假如我们把 coin 逆序排列,然后逐个取,取到刚好不大于 amout,依次类推。
65+
假如我们把 coin 逆序排列,然后从面值大的开始取,如果取了当前硬币后金额仍有小于 amount,则继续取。
66+
67+
举个例子:
6668

6769
```
6870
eg: 对于 [1,2,5] 组成 11 块
@@ -86,17 +88,33 @@ eg: 对于 [1,2,5] 组成 11 块
8688
因此结果是 3
8789
```
8890

89-
熟悉贪心算法的同学应该已经注意到了,这就是贪心算法,贪心算法更 amount 尽快地变得更小。
90-
`经验表明,贪心策略是正确的`。 注意,我说的是经验表明, 贪心算法也有可能出错。 就拿这道题目来说,
91-
他也是不正确的! 比如 `coins = [1, 5, 11] amout = 15`, 因此这种做法有时候不靠谱,我们还是采用靠谱的做法.
91+
熟悉贪心算法的同学应该已经注意到了,这就是贪心算法,贪心算法想要使得 amount **尽快地变得更小**
92+
93+
贪心算法通常时间复杂度更低,但对这道题目来说,贪心是不正确的!要证明它的错误,只需要随便举一个反例即可。 比如 `coins = [1, 5, 11] amout = 15`, 使用贪心就会得到错误的结果。 因此这种做法有时候不靠谱,我们还是采用靠谱的做法.
94+
95+
如果我们暴力求解,对于所有的组合都计算一遍,然后比较, 那么这样的复杂度是 2 的 n 次方(这个可以通过数学公式证明,这里不想啰嗦了),这个是不可以接受的。
96+
97+
暴力法枚举过程实际上有很多重复子问题,我们一般称重叠子问题。对于重叠子问题,我们可以使用备忘录来解决。
98+
99+
以 coins = [1,2,3], amount = 6 来说,我们可以画出如下的递归树。
100+
101+
![](https://tva1.sinaimg.cn/large/008eGmZEly1goil47ztakj31jk0nm791.jpg)
102+
103+
(图片来自https://leetcode.com/problems/coin-change/solution/)
104+
105+
如上图 F(1) 被重复计算了 13 次!!如何消除重叠子问题?答案是记忆化递归或者动态规划,二者没有本质区别。
106+
107+
这里以自底向上的动态规划为例讲解一下。比较容易想到的是二维数组存放 F(n) 。
92108

93-
如果我们暴力求解,对于所有的组合都计算一遍,然后比较, 那么这样的复杂度是 2 的 n 次方(这个可以通过数学公式证明,这里不想啰嗦了),
94-
这个是不可以接受的。那么我们是否可以动态规划解决呢?答案是可以,原因就是可以划分为子问题,子问题可以推导出原问题
109+
定义 dp[i][j] 为使用 coins 的前 j 项组成 金额 i 的最少硬币数。对于动态规划问题,最关键的是决策(不包含决策的是递推式动态规划)。对于动态规划的决策的技巧就是:仅关心最后一步和前一步,不考虑其他部分是如何达成的。
95110

96-
对于动态规划我们可以先画一个二维表,然后观察,其是否可以用一维表代替。
97-
关于动态规划为什么要画表,我已经在[这篇文章](../thinkings/dynamic-programming.md)解释了
111+
对于这道题来说,最后一步就是选择第 j 个硬币还是不选择第 j 个硬币。
98112

99-
比较容易想到的是二维数组:
113+
- 如果选择第 j 个硬币,那么 dp[i][j] = min(dp[i][j - 1], dp[i - coins[j - 1]][j] + 1)
114+
115+
> 注意:dp[i - coins[j - 1]][j] 含义是硬币无限取, dp[i - coins[j - 1]][j - 1] 的含义就变成了硬币最多取一次
116+
117+
- 否则 dp[i][j] = dp[i][j - 1]
100118

101119
```python
102120
class Solution:
@@ -127,23 +145,19 @@ class Solution:
127145
- 时间复杂度:$O(amonut * len(coins))$
128146
- 空间复杂度:$O(amount * len(coins))$
129147

130-
dp[i][j] 依赖于`dp[i][j - 1]``dp[i - coins[j - 1]][j] + 1)` 这是一个优化的信号,我们可以将其优化到一维,具体见下方。
131-
132-
## 关键点解析
133-
134-
- 动态规划
135-
136-
- 子问题
148+
dp[i][j] 依赖于`dp[i][j - 1]``dp[i - coins[j - 1]][j] + 1)` 这是一个优化的信号,我们可以将其优化到一维。
137149

138150
用 dp[i] 来表示组成 i 块钱,需要最少的硬币数,那么
139151

140152
1. 第 j 个硬币我可以选择不拿 这个时候, 硬币数 = dp[i]
141153

142154
2. 第 j 个硬币我可以选择拿 这个时候, 硬币数 = dp[i - coins[j]] + 1
143155

144-
- 和背包问题不同, 硬币是可以拿任意个
156+
和 01 背包问题不同, 硬币是可以拿任意个,对于每一个 dp[i] 我们都选择遍历一遍 coin, 不断更新 dp[i]
157+
158+
## 关键点解析
145159

146-
- 对于每一个 dp[i] 我们都选择遍历一遍 coin, 不断更新 dp[i]
160+
- 分析出是典型的完全背包问题
147161

148162
## 代码
149163

0 commit comments

Comments
 (0)