并行课程 III
难度:
标签:
题目描述
给你一个整数 n
,表示有 n
节课,课程编号从 1
到 n
。同时给你一个二维整数数组 relations
,其中 relations[j] = [prevCoursej, nextCoursej]
,表示课程 prevCoursej
必须在课程 nextCoursej
之前 完成(先修课的关系)。同时给你一个下标从 0 开始的整数数组 time
,其中 time[i]
表示完成第 (i+1)
门课程需要花费的 月份 数。
请你根据以下规则算出完成所有课程所需要的 最少 月份数:
- 如果一门课的所有先修课都已经完成,你可以在 任意 时间开始这门课程。
- 你可以 同时 上 任意门课程 。
请你返回完成所有课程所需要的 最少 月份数。
注意:测试数据保证一定可以完成所有课程(也就是先修课的关系构成一个有向无环图)。
示例 1:
输入:n = 3, relations = [[1,3],[2,3]], time = [3,2,5] 输出:8 解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 你可以在月份 0 同时开始课程 1 和 2 。 课程 1 花费 3 个月,课程 2 花费 2 个月。 所以,最早开始课程 3 的时间是月份 3 ,完成所有课程所需时间为 3 + 5 = 8 个月。
示例 2:
输入:n = 5, relations = [[1,5],[2,5],[3,5],[3,4],[4,5]], time = [1,2,3,4,5] 输出:12 解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。 你可以在月份 0 同时开始课程 1 ,2 和 3 。 在月份 1,2 和 3 分别完成这三门课程。 课程 4 需在课程 3 之后开始,也就是 3 个月后。课程 4 在 3 + 4 = 7 月完成。 课程 5 需在课程 1,2,3 和 4 之后开始,也就是在 max(1,2,3,7) = 7 月开始。 所以完成所有课程所需的最少时间为 7 + 5 = 12 个月。
提示:
1 <= n <= 5 * 104
0 <= relations.length <= min(n * (n - 1) / 2, 5 * 104)
relations[j].length == 2
1 <= prevCoursej, nextCoursej <= n
prevCoursej != nextCoursej
- 所有的先修课程对
[prevCoursej, nextCoursej]
都是 互不相同 的。 time.length == n
1 <= time[i] <= 104
- 先修课程图是一个有向无环图。
代码结果
运行时间: 157 ms, 内存: 36.5 MB
/*
* 思路:
* 1. 使用拓扑排序找到课程的完成顺序。
* 2. 创建一个数组存储每个课程的最早完成时间。
* 3. 对于每门课程,计算所有先修课程完成后再加上当前课程所需的时间。
* 4. 返回所有课程中最早完成时间的最大值。
* 5. 使用Java Stream API来处理数据流。
*/
import java.util.*;
import java.util.stream.*;
public class Solution {
public int minimumTime(int n, int[][] relations, int[] time) {
Map<Integer, List<Integer>> graph = IntStream.rangeClosed(1, n)
.boxed()
.collect(Collectors.toMap(i -> i, i -> new ArrayList<>()));
int[] inDegree = new int[n + 1];
Arrays.stream(relations).forEach(relation -> {
graph.get(relation[0]).add(relation[1]);
inDegree[relation[1]]++;
});
Queue<Integer> queue = IntStream.rangeClosed(1, n)
.filter(i -> inDegree[i] == 0)
.boxed()
.collect(Collectors.toCollection(LinkedList::new));
int[] earliestTime = IntStream.rangeClosed(1, n)
.map(i -> time[i - 1])
.toArray();
while (!queue.isEmpty()) {
int curr = queue.poll();
graph.get(curr).forEach(next -> {
earliestTime[next] = Math.max(earliestTime[next], earliestTime[curr] + time[next - 1]);
if (--inDegree[next] == 0) {
queue.offer(next);
}
});
}
return Arrays.stream(earliestTime).max().getAsInt();
}
}
解释
方法:
本题解采用拓扑排序的方法来解决有向无环图中的课程安排问题。首先,构建图的邻接表和每个节点的入度数组。使用队列来实现拓扑排序,其中队列初始包含所有入度为0的节点(即没有先修课程的课程)。在拓扑排序的过程中,我们同时计算每门课程的最早完成时间。对于每个从队列中取出的节点,更新其后继节点的最早开始时间,如果后继节点的所有前驱节点都已处理,该节点也被加入队列。最终,遍历所有课程的最早完成时间,取最大值即为完成所有课程所需的最少月份数。
时间复杂度:
O(n + E)
空间复杂度:
O(n + E)
代码细节讲解
🦆
在拓扑排序中,你是如何确保每个节点只被处理一次,从而保持算法的效率?
▷🦆
在构建图的邻接表时,为什么选择使用入度数组来帮助实现拓扑排序?其有什么特别的优势?
▷🦆
算法中使用了队列来存储入度为0的节点,这种方法与使用栈有何不同?会对最终的拓扑排序结果有何影响?
▷🦆
如果有两个或多个节点同时入度为0,算法如何决定哪个节点先处理?这会影响最终的课程完成月份吗?
▷