leetcode
leetcode 1301 ~ 1350
课程表 IV

课程表 IV

难度:

标签:

题目描述

你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite ,其中 prerequisites[i] = [ai, bi] 表示如果你想选 bi 课程,你 必须 先选 ai 课程。

  • 有的课会有直接的先修课程,比如如果想上课程 1 ,你必须先上课程 0 ,那么会以 [0,1] 数对的形式给出先修课程数对。

先决条件也可以是 间接 的。如果课程 a 是课程 b 的先决条件,课程 b 是课程 c 的先决条件,那么课程 a 就是课程 c 的先决条件。

你也得到一个数组 queries ,其中 queries[j] = [uj, vj]。对于第 j 个查询,您应该回答课程 uj 是否是课程 vj 的先决条件。

返回一个布尔数组 answer ,其中 answer[j] 是第 j 个查询的答案。

 

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
输出:[false,true]
解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。

示例 2:

输入:numCourses = 2, prerequisites = [], queries = [[1,0],[0,1]]
输出:[false,false]
解释:没有先修课程对,所以每门课程之间是独立的。

示例 3:

输入:numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
输出:[true,true]

 

提示:

  • 2 <= numCourses <= 100
  • 0 <= prerequisites.length <= (numCourses * (numCourses - 1) / 2)
  • prerequisites[i].length == 2
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • 每一对 [ai, bi] 都 不同
  • 先修课程图中没有环。
  • 1 <= queries.length <= 104
  • 0 <= ui, vi <= n - 1
  • ui != vi

代码结果

运行时间: 80 ms, 内存: 18.9 MB


/*
 * 思路:
 * 同样使用Floyd-Warshall算法,我们可以利用Java Stream API来简化某些操作。
 * 首先,我们初始化一个二维布尔数组reachable,并使用Stream API来更新它。
 * 然后,我们使用Floyd-Warshall算法更新reachable数组,并在此过程中使用Stream API来进行遍历。
 * 最后,我们再次使用Stream API来处理查询数组,并根据reachable数组的值来回答每个查询。
 */
import java.util.stream.IntStream;
import java.util.stream.Stream;
public boolean[] checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
    boolean[][] reachable = new boolean[numCourses][numCourses];
    Stream.of(prerequisites).forEach(prereq -> reachable[prereq[0]][prereq[1]] = true);
    IntStream.range(0, numCourses).forEach(k ->
        IntStream.range(0, numCourses).forEach(i ->
            IntStream.range(0, numCourses).forEach(j -> {
                if (reachable[i][k] && reachable[k][j]) {
                    reachable[i][j] = true;
                }
            })
        )
    );
    return IntStream.range(0, queries.length)
            .mapToObj(i -> reachable[queries[i][0]][queries[i][1]])
            .mapToBoolean(Boolean::booleanValue)
            .toArray();
}

解释

方法:

该题解使用深度优先搜索(DFS)来确定课程之间的依赖关系。首先,每个课程的入度和一个用于存储每个课程所有先决课程的集合(father)被初始化。题解将先修关系的方向反转,即如果课程u是课程v的先修,则在v到u之间建立一条边。对所有入度为0的课程,也就是没有任何先修要求的课程,执行DFS。在DFS过程中,每访问一个课程,将其直接依赖课程加入到它的father集合中,并合并所有这些依赖课程的father集合。最后,根据查询数组,检查每个查询中的课程u是否在课程v的father集合中,以确定是否是先修关系。

时间复杂度:

O(n^2 + q)

空间复杂度:

O(n^2)

代码细节讲解

🦆
在题解中,为什么选择使用深度优先搜索(DFS)而不是广度优先搜索(BFS)来确定课程之间的依赖关系?
在题解中选择使用DFS而不是BFS的主要原因是DFS在这种情况下可以更方便地收集和传播每个节点(课程)的所有依赖关系。使用DFS时,当探索到某一课程的所有依赖课程后,可以在递归回溯过程中方便地将这些依赖信息一并传递回上层节点。这种传播机制在BFS中实现起来较为复杂,因为BFS通常是逐层处理,需要额外的结构来存储和更新每个节点的依赖信息。
🦆
题解中提到,对所有入度为0的课程执行DFS。这种方法是否意味着在某些课程的依赖关系为环形的情况下,这些课程将不会被处理?
题解中提到对所有入度为0的课程执行DFS是基于这些课程没有前置依赖,可以直接开始探索。对于存在环的课程,因为环内各课程的入度都不为0(每个课程至少有一个前置依赖),这些课程不会被初始化启动DFS。然而,这并不意味着这些课程不会被处理;它们将在DFS过程中通过其他入度为0的课程间接触达。如果所有课程都形成一个闭环,则这种方法确实无法处理,因为没有入度为0的课程来启动DFS。
🦆
题解中使用反向边构建图的目的是什么?这样做与正常边构建相比有什么优势或不同之处?
题解中使用反向边构建图是为了便于从任何一个课程开始,能够直接访问到这个课程的所有先修课程,即可以直接追溯到所有可能的先修路径。这种建图方式与常规的先修关系建图(从先修课程指向后续课程的边)相比,使得在确定一个课程的所有可能的先修课程时更为直接和高效,因为可以直接从课程出发,向上追溯所有依赖,而不需要进行额外的查找或反向追踪操作。
🦆
题解中提到通过检查课程u是否在课程v的father集合中来确定是否是先修关系。这种方法在处理大量课程和查询时的性能表现如何?
这种方法在处理大量课程和查询时的性能主要取决于两个方面:构建每个课程的father集合的效率,以及查询效率。构建集合的时间复杂度主要由图的结构和DFS的效率决定,而每次查询的时间复杂度为O(1),因为只需检查集合成员关系。然而,如果课程数量非常大,每个课程的father集合可能变得非常大,这将增加内存使用量并可能影响构建集合的时间。在极端情况下,处理大量密集的依赖关系和查询可能导致性能瓶颈,特别是在内存占用和查询响应时间上。

相关问题