@@ -62,7 +62,9 @@ https://leetcode-cn.com/problems/coin-change/
62
62
63
63
## 思路
64
64
65
- 假如我们把 coin 逆序排列,然后逐个取,取到刚好不大于 amout,依次类推。
65
+ 假如我们把 coin 逆序排列,然后从面值大的开始取,如果取了当前硬币后金额仍有小于 amount,则继续取。
66
+
67
+ 举个例子:
66
68
67
69
```
68
70
eg: 对于 [1,2,5] 组成 11 块
@@ -86,17 +88,33 @@ eg: 对于 [1,2,5] 组成 11 块
86
88
因此结果是 3
87
89
```
88
90
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) 。
92
108
93
- 如果我们暴力求解,对于所有的组合都计算一遍,然后比较, 那么这样的复杂度是 2 的 n 次方(这个可以通过数学公式证明,这里不想啰嗦了),
94
- 这个是不可以接受的。那么我们是否可以动态规划解决呢?答案是可以,原因就是可以划分为子问题,子问题可以推导出原问题
109
+ 定义 dp[ i] [ j ] 为使用 coins 的前 j 项组成 金额 i 的最少硬币数。对于动态规划问题,最关键的是决策(不包含决策的是递推式动态规划)。对于动态规划的决策的技巧就是:仅关心最后一步和前一步,不考虑其他部分是如何达成的。
95
110
96
- 对于动态规划我们可以先画一个二维表,然后观察,其是否可以用一维表代替。
97
- 关于动态规划为什么要画表,我已经在[ 这篇文章] ( ../thinkings/dynamic-programming.md ) 解释了
111
+ 对于这道题来说,最后一步就是选择第 j 个硬币还是不选择第 j 个硬币。
98
112
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 ]
100
118
101
119
``` python
102
120
class Solution :
@@ -127,23 +145,19 @@ class Solution:
127
145
- 时间复杂度:$O(amonut * len(coins))$
128
146
- 空间复杂度:$O(amount * len(coins))$
129
147
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) ` 这是一个优化的信号,我们可以将其优化到一维。
137
149
138
150
用 dp[ i] 来表示组成 i 块钱,需要最少的硬币数,那么
139
151
140
152
1 . 第 j 个硬币我可以选择不拿 这个时候, 硬币数 = dp[ i]
141
153
142
154
2 . 第 j 个硬币我可以选择拿 这个时候, 硬币数 = dp[ i - coins[ j]] + 1
143
155
144
- - 和背包问题不同, 硬币是可以拿任意个
156
+ 和 01 背包问题不同, 硬币是可以拿任意个,对于每一个 dp[ i] 我们都选择遍历一遍 coin, 不断更新 dp[ i]
157
+
158
+ ## 关键点解析
145
159
146
- - 对于每一个 dp [ i ] 我们都选择遍历一遍 coin, 不断更新 dp [ i ]
160
+ - 分析出是典型的完全背包问题
147
161
148
162
## 代码
149
163
0 commit comments