Course Schedule IV
Problem Description
There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ui, vi] indicates that you must take course ui before course vi.
Prerequisites are transitive: if course A is a prerequisite of course B, and course B is a prerequisite of course C, then course A is a prerequisite of course C.
You are also given an array queries where queries[j] = [uj, vj]. For each query j, answer whether course uj is a prerequisite of course vj.
Return a boolean array answer, where answer[j] is the answer to the jth query.
Examples
Example 1:Input: numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]] Output: [false,true] Explanation: The database shows that course 1 must be taken before course 0. So 0 is not a prerequisite of 1 (false), but 1 is a prerequisite of 0 (true).
Input: numCourses = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]] Output: [true,true]
Constraints
2 ≤ numCourses ≤ 1000 ≤ prerequisites.length ≤ numCourses * (numCourses - 1) / 2prerequisites[i].length == 20 ≤ ui, vi < numCoursesui != vi- All pairs
[ui, vi]are distinct 1 ≤ queries.length ≤ 10⁴queries[j].length == 20 ≤ uj, vj < numCoursesuj != vj
Reachability Matrix
Because numCourses is very small (≤ 100), we can build a full reachability matrix where reachable[i][j] is true if i is a prerequisite of j.
We can populate this matrix using the Floyd-Warshall algorithm in O(V³) time. For every intermediate course k, if i reaches k and k reaches j, then i reaches j. Once populated, each query is answered in O(1) time.
Solution 1: Floyd-Warshall (O(V³) Precomputation)
import java.util.*;
class Solution {
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
boolean[][] isPre = new boolean[numCourses][numCourses];
for (int[] pre : prerequisites) {
isPre[pre[0]][pre[1]] = true;
}
// Floyd-Warshall transitive closure
for (int k = 0; k < numCourses; k++) {
for (int i = 0; i < numCourses; i++) {
for (int j = 0; j < numCourses; j++) {
if (isPre[i][k] && isPre[k][j]) {
isPre[i][j] = true;
}
}
}
}
List<Boolean> result = new ArrayList<>();
for (int[] q : queries) {
result.add(isPre[q[0]][q[1]]);
}
return result;
}
}class Solution:
def checkIfPrerequisite(self, numCourses: int, prerequisites: list[list[int]],
queries: list[list[int]]) -> list[bool]:
is_pre = [[False] * numCourses for _ in range(numCourses)]
for src, dest in prerequisites:
is_pre[src][dest] = True
# Floyd-Warshall transitive closure
for k in range(numCourses):
for i in range(numCourses):
for j in range(numCourses):
if is_pre[i][k] and is_pre[k][j]:
is_pre[i][j] = True
return [is_pre[u][v] for u, v in queries]#include <vector>
class Solution {
public:
std::vector<bool> checkIfPrerequisite(int numCourses, std::vector<std::vector<int>>& prerequisites,
std::vector<std::vector<int>>& queries) {
std::vector<std::vector<bool>> isPre(numCourses, std::vector<bool>(numCourses, false));
for (const auto& pre : prerequisites) {
isPre[pre[0]][pre[1]] = true;
}
for (int k = 0; k < numCourses; k++) {
for (int i = 0; i < numCourses; i++) {
for (int j = 0; j < numCourses; j++) {
if (isPre[i][k] && isPre[k][j]) {
isPre[i][j] = true;
}
}
}
}
std::vector<bool> result;
for (const auto& q : queries) {
result.push_back(isPre[q[0]][q[1]]);
}
return result;
}
};Complexity Analysis:
- Time Complexity: O(V³ + Q) where V is
numCoursesand Q is the number of queries. Floyd-Warshall runs in O(V³) time, which is fast when V ≤ 100 (1 million iterations). - Space Complexity: O(V²) to store the reachability matrix.
Where it breaks: if numCourses were larger (e.g., 2000), Floyd-Warshall’s O(V³) time complexity (8 billion operations) would time out. In that case, doing a BFS/DFS from each node to build prerequisite sets is better, running in O(V * (V + E)).
Solution 2: BFS Precomputation (O(V * (V + E)))
Do a BFS from each course i to find all descendant courses, marking isPre[i][j] = true for each visited course j.
import java.util.*;
class Solution {
public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>());
for (int[] pre : prerequisites) {
adj.get(pre[0]).add(pre[1]);
}
boolean[][] isPre = new boolean[numCourses][numCourses];
// run BFS from each node
for (int i = 0; i < numCourses; i++) {
Queue<Integer> q = new LinkedList<>();
q.offer(i);
while (!q.isEmpty()) {
int curr = q.poll();
for (int neighbor : adj.get(curr)) {
if (!isPre[i][neighbor]) {
isPre[i][neighbor] = true;
q.offer(neighbor);
}
}
}
}
List<Boolean> result = new ArrayList<>();
for (int[] q : queries) {
result.add(isPre[q[0]][q[1]]);
}
return result;
}
}from collections import deque
class Solution:
def checkIfPrerequisite(self, numCourses: int, prerequisites: list[list[int]],
queries: list[list[int]]) -> list[bool]:
adj = [[] for _ in range(numCourses)]
for src, dest in prerequisites:
adj[src].append(dest)
is_pre = [[False] * numCourses for _ in range(numCourses)]
for i in range(numCourses):
queue = deque([i])
while queue:
curr = queue.popleft()
for neighbor in adj[curr]:
if not is_pre[i][neighbor]:
is_pre[i][neighbor] = True
queue.append(neighbor)
return [is_pre[u][v] for u, v in queries]#include <vector>
#include <queue>
class Solution {
public:
std::vector<bool> checkIfPrerequisite(int numCourses, std::vector<std::vector<int>>& prerequisites,
std::vector<std::vector<int>>& queries) {
std::vector<std::vector<int>> adj(numCourses);
for (const auto& pre : prerequisites) {
adj[pre[0]].push_back(pre[1]);
}
std::vector<std::vector<bool>> isPre(numCourses, std::vector<bool>(numCourses, false));
for (int i = 0; i < numCourses; i++) {
std::queue<int> q;
q.push(i);
while (!q.empty()) {
int curr = q.front(); q.pop();
for (int neighbor : adj[curr]) {
if (!isPre[i][neighbor]) {
isPre[i][neighbor] = true;
q.push(neighbor);
}
}
}
}
std::vector<bool> result;
for (const auto& q : queries) {
result.push_back(isPre[q[0]][q[1]]);
}
return result;
}
};Complexity Analysis:
- Time Complexity: O(V * (V + E) + Q). We perform a BFS traversal from each of the V nodes.
- Space Complexity: O(V² + E) to store the adjacency list and the reachability matrix.