@@ -63,6 +63,8 @@ xi != yi
63
63
64
64
> 一般 20 以内都可以,具体的时间复杂度和数据规模的关系可从[ 我的网站] ( https://leetcode-pp.github.io/leetcode-cheat/ ) 中的复杂度速查表中查看。
65
65
66
+ 再比如 [ 5289. 公平分发饼干] ( https://leetcode.cn/problems/fair-distribution-of-cookies/ ) 看下数据范围就大概知道很可能就是回溯或者状压 DP。
67
+
66
68
这道题是状压 DP,如果你对其不了解,可以看下我之前写的 [ 状压 DP 是什么?这篇题解带你入门] ( https://mp.weixin.qq.com/s/ecxTTrRvUJbdWwSFbKgDiw ) ,这里一些细节不再赘述。
67
69
68
70
首先,我们需要用一个数据结构来存储课程之间的依赖关系。不妨使用 hashmap,这样可以在 $O(1)$ 的时间获取到一个课程的所有前置课程。
@@ -134,11 +136,17 @@ for (i = x; i != 0; i = (i - 1) & x) {
134
136
135
137
有了上面的铺垫就简单了。我们只需要枚举所有子集,对于每一个子集,我们考虑使用动态规划来转移状态。
136
138
139
+ 定义 dp[ studied] 为学习情况为 studied 的最少需要学期数。
140
+
141
+ 那么转移方程为:
142
+
137
143
``` py
138
- dp[i | sub] = min (dp[i | sub], dp[i] + 1 )
144
+ # 含义为我们可以选择在这一学期学习 sub,或者选择在下一学期学习 sub
145
+ # dp[studied | sub] 就是两种选择的较小值
146
+ dp[studied | sub] = min (dp[studied | sub], dp[studied] + 1 )
139
147
```
140
148
141
- 其中 i 为当前的学习的课程,sub 为当前可以学习的课程的子集。其中 i 和 sub 都是一个数字,每一位的 bit 为 0 表示无该课程,为 1 表示有该课程。
149
+ 其中 studied 为当前的学习的课程,sub 为当前可以学习的课程的子集。其中 studied 和 sub 都是一个数字,每一位的 bit 为 0 表示无该课程,为 1 表示有该课程。
142
150
143
151
## 关键点
144
152
@@ -160,7 +168,7 @@ class Solution:
160
168
161
169
for fr, to in dependencies:
162
170
neighbors[to - 1 ] |= 1 << (fr - 1 )
163
- dp[0 ] = 0 # 启动 dp
171
+ dp[0 ] = 0 # 表示什么都不学的情况需要 0 学期
164
172
for i in range (1 << n):
165
173
can = 0
166
174
for j in range (n):
@@ -170,9 +178,10 @@ class Solution:
170
178
can &= ~ i
171
179
sub = can
172
180
while sub:
181
+ # 可以学习 sub
173
182
if bin (sub).count(" 1" ) <= k:
174
183
dp[i | sub] = min (dp[i | sub], dp[i] + 1 )
175
- sub = (sub - 1 ) & can
184
+ sub = (sub - 1 ) & can # 快速跳到下一个子集(枚举子集优化)
176
185
return dp[(1 << n) - 1 ]
177
186
178
187
0 commit comments