并行课程 II
难度:
标签:
题目描述
给你一个整数 n
表示某所大学里课程的数目,编号为 1
到 n
,数组 relations
中, relations[i] = [xi, yi]
表示一个先修课的关系,也就是课程 xi
必须在课程 yi
之前上。同时你还有一个整数 k
。
在一个学期中,你 最多 可以同时上 k
门课,前提是这些课的先修课在之前的学期里已经上过了。
请你返回上完所有课最少需要多少个学期。题目保证一定存在一种上完所有课的方式。
示例 1:
输入:n = 4, relations = [[2,1],[3,1],[1,4]], k = 2 输出:3 解释:上图展示了题目输入的图。在第一个学期中,我们可以上课程 2 和课程 3 。然后第二个学期上课程 1 ,第三个学期上课程 4 。
示例 2:
输入:n = 5, relations = [[2,1],[3,1],[4,1],[1,5]], k = 2 输出:4 解释:上图展示了题目输入的图。一个最优方案是:第一学期上课程 2 和 3,第二学期上课程 4 ,第三学期上课程 1 ,第四学期上课程 5 。
示例 3:
输入:n = 11, relations = [], k = 2 输出:6
提示:
1 <= n <= 15
1 <= k <= n
0 <= relations.length <= n * (n-1) / 2
relations[i].length == 2
1 <= xi, yi <= n
xi != yi
- 所有先修关系都是不同的,也就是说
relations[i] != relations[j]
。 - 题目输入的图是个有向无环图。
代码结果
运行时间: 67 ms, 内存: 16.2 MB
/*
* 题目思路:
* 1. 使用拓扑排序来计算课程的先修关系。
* 2. 对于每个学期,选择最多k门可以上的课程,确保所有先修课都已完成。
* 3. 使用动态规划记录当前已完成课程的状态,并计算最少学期数。
* 4. 使用Java Stream简化操作和代码。
*/
import java.util.*;
import java.util.stream.*;
public class Solution {
public int minNumberOfSemesters(int n, int[][] relations, int k) {
int[] prerequisites = new int[n];
Arrays.stream(relations).forEach(relation -> prerequisites[relation[1] - 1] |= (1 << (relation[0] - 1)));
int[] dp = new int[1 << n];
Arrays.fill(dp, n + 1);
dp[0] = 0;
IntStream.range(0, (1 << n)).forEach(state -> {
int availableCourses = IntStream.range(0, n)
.filter(i -> (state & (1 << i)) == 0 && (state & prerequisites[i]) == prerequisites[i])
.map(i -> (1 << i))
.reduce(0, (a, b) -> a | b);
for (int sub = availableCourses; sub > 0; sub = (sub - 1) & availableCourses) {
if (Integer.bitCount(sub) <= k) {
dp[state | sub] = Math.min(dp[state | sub], dp[state] + 1);
}
}
});
return dp[(1 << n) - 1];
}
}
解释
方法:
该题解采用了状态压缩动态规划的思路。首先用一个整数表示每门课程的先修课状态,然后枚举所有可能的已上课程状态,对于每个状态,如果能找到一个不超过k门可上的先修课程组合,就可以推出这个状态的最少学期数。状态转移方程为f[i] = min(f[i], f[i^j] + 1),其中i是当前状态,j是当前状态的一个不超过k门课的子集。最终答案为f[-1],即全部课程都上完的状态所需的最少学期数。在此基础上,题解还用随机化算法进行了预处理,多次随机打乱课程顺序,计算需要的最少学期数,取最小值作为答案,以尽量逼近最优解。
时间复杂度:
O(2^n * n^k)
空间复杂度:
O(2^n)
代码细节讲解
🦆
在使用状态压缩动态规划时,你是如何确定使用整数来表示每门课程的先修课状态的?
▷🦆
题解中提到的随机化算法为什么会进行80次尝试?这个数字是如何确定的,它与算法的准确性和效率有什么关系?
▷🦆
在动态规划中,状态转移方程`f[i] = min(f[i], f[i^j] + 1)`里的`i^j`操作是什么意思?它如何帮助确定状态转移?
▷