题目地址:
https://leetcode.com/problems/parallel-courses-ii/
给定 n n n门课,编号 1 ∼ n 1\sim n 1∼n,两个课程之间可能有前置关系,例如 u → v u\to v u→v表示 u u u是 v v v的一门前置课程,想上 v v v必须先上完 u u u。每门课可能有多门前置课程。规定每学期最多上 k k k门课,并且每学期上的课里必须每一门的前置课程都已经修好(当然没有前置课程也可以)。问最少多少个学期可以将所有课都上完。题目保证课程不会产生环。
思路是动态规划。设课程编号从 0 0 0开始。设 f [ s ] f[s] f[s]是要上完课程的二进制表示是 s s s的情况下,最少需要多少个学期。那么 f [ 0 ] = 0 f[0]=0 f[0]=0。我们考虑 f [ s ] f[s] f[s]可以更新哪些状态。首先我们找到在状态 s s s下可以上哪些还没上过的课,设这些课的二进制表示为 t t t,然后遍历 t t t的所有非空子集,如果某子集 p p p的 1 1 1的个数小于 k k k,那么这些课是可以在下个学期里上完的,从而 f [ s ] + 1 f[s]+1 f[s]+1可以用来更新 f [ s ∣ p ] f[s|p] f[s∣p]。最后返回 f [ 2 n − 1 ] f[2^n-1] f[2n−1]即可。代码如下:
class Solution {
public:
int minNumberOfSemesters(int n, vector<vector<int>>& rs, int k) {
vector<int> f(1 << n, n), pre(n);
// 存一下每门课的前置课程是哪些
for (auto& r : rs) pre[r[1] - 1] |= 1 << r[0] - 1;
f[0] = 0;
for (int i = 0; i < (1 << n); i++) {
// 求一下当前已经上完的课是i的情况下,下学期可以上哪些课
int can_take = 0;
for (int j = 0; j < n; j++)
// 如果j的所有前置课程都包含在i内,那j是可以上的
if ((pre[j] & i) == pre[j]) can_take |= 1 << j;
// 把已经上过的课排除掉
can_take &= ~i;
// 遍历p的所有子集。p - 1 & can_take是p的真子集里二进制表示最大的那个
for (int p = can_take; p; p = p - 1 & can_take)
// 如果p的1的个数小于等于k,那么下学期可以选择上p的那些课
if (__builtin_popcount(p) <= k) f[i | p] = min(f[i | p], f[i] + 1);
}
return f[(1 << n) - 1];
}
};
时间复杂度 O ( n 2 n ) O(n2^n) O(n2n),空间 O ( 2 n ) O(2^n) O(2n)。