Lesson 160 of 250
1462. Course Schedule IV
NeetCode: Course Schedule IV · LeetCode: 1462. Course Schedule IV · Difficulty: Medium
There are numCourses courses labelled 0 to numCourses - 1. Each pair [a, b] in prerequisites means you must take a before b. Prerequisites are transitive: if a is a prerequisite of b and b of c, then a is a prerequisite of c. For each query [u, v], answer whether u is a (direct or indirect) prerequisite of v. The graph has no cycles.
Example
Input: numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
Output: [false, true]
Input: numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
Output: [true, true]
Input: numCourses = 3, prerequisites = [], queries = [[1,0],[1,2]]
Output: [false, false]Brute force: a DFS for every query
Build an adjacency list a -> b. To answer [u, v], run a DFS from u and report whether v is reachable. Each query re-explores the graph from scratch, even though many queries share the same source. The adjacency list is a List<List<Integer>>.
import java.util.ArrayList;
import java.util.List;
public class Main {
static class Solution {
private boolean dfs(List<List<Integer>> graph, int node, int target, boolean[] seen) {
if (node == target) {
return true;
}
seen[node] = true;
for (int next : graph.get(node)) {
if (!seen[next] && dfs(graph, next, target, seen)) {
return true;
}
}
return false;
}
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int[] p : prerequisites) {
graph.get(p[0]).add(p[1]);
}
List<Boolean> answer = new ArrayList<>();
for (int[] q : queries) {
answer.add(dfs(graph, q[0], q[1], new boolean[numCourses]));
}
return answer;
}
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.checkIfPrerequisite(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
int[][] pre = {{1, 2}, {1, 0}, {2, 0}};
System.out.println(s.checkIfPrerequisite(3, pre, new int[][]{{1, 0}, {1, 2}}));
System.out.println(s.checkIfPrerequisite(3, new int[][]{}, new int[][]{{1, 0}, {1, 2}}));
}
}[false, true]
[true, true]
[false, false]With V courses, E prerequisites and Q queries, each query costs O(V + E). Time: O(Q * (V + E)) plus O(V + E) to build the graph. Space: O(V + E).
Optimal: transitive closure (Floyd-Warshall style)
Since all queries are about reachability, compute the answer for every pair once. Let reach[i][j] be true if i is a prerequisite of j. Start with the direct edges, then for each intermediate course k, set reach[i][j] = true whenever reach[i][k] and reach[k][j]. k must be the outermost loop: after processing k, every path whose intermediate courses are all in 0 to k is recorded. The result is a List<Boolean>, and answer.add(boolean) boxes each value to Boolean. Each query is then an O(1) lookup.
import java.util.ArrayList;
import java.util.List;
public class Main {
static class Solution {
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
int n = numCourses;
boolean[][] reach = new boolean[n][n];
for (int[] p : prerequisites) {
reach[p[0]][p[1]] = true;
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
if (!reach[i][k]) {
continue;
}
for (int j = 0; j < n; j++) {
if (reach[k][j]) {
reach[i][j] = true;
}
}
}
}
List<Boolean> answer = new ArrayList<>();
for (int[] q : queries) {
answer.add(reach[q[0]][q[1]]);
}
return answer;
}
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.checkIfPrerequisite(2, new int[][]{{1, 0}}, new int[][]{{0, 1}, {1, 0}}));
int[][] pre = {{1, 2}, {1, 0}, {2, 0}};
System.out.println(s.checkIfPrerequisite(3, pre, new int[][]{{1, 0}, {1, 2}}));
System.out.println(s.checkIfPrerequisite(3, new int[][]{}, new int[][]{{1, 0}, {1, 2}}));
}
}[false, true]
[true, true]
[false, false]The closure costs O(V^3) once and each query is then a lookup. It pays off when Q is large compared with V^2; for few queries on a large sparse graph the per-query DFS can be cheaper. Running a DFS or BFS from every node, or keeping a bitset of ancestors per node in topological order, are alternatives. Time: O(V^3 + E + Q). Space: O(V^2).
