Parallel Courses
Minimum number of semesters to complete n courses with dependencies, unlimited parallelism — BFS over semester levels.
4 min read
topological-sort bfs
Problem#
n courses 1..n with dependency pairs [a, b] meaning a before b. Each semester you can take any subset whose prereqs are done. Return minimum semesters, or -1 if cyclic.
Examples#
n=3, relations=[[1,3],[2,3]]→2n=3, relations=[[1,2],[2,3],[3,1]]→-1
Approach#
Layered BFS (Kahn’s by depth). Each round, dequeue every current zero-indegree node, decrement their neighbors, enqueue new zeros. Increment semester counter per round.
Solution#
class Solution {public: int minimumSemesters(int n, vector<vector<int>>& relations) { vector<vector<int>> adj(n + 1); vector<int> indeg(n + 1, 0); for (auto& r : relations) { adj[r[0]].push_back(r[1]); ++indeg[r[1]]; } queue<int> q; for (int i = 1; i <= n; ++i) if (indeg[i] == 0) q.push(i); int sem = 0, taken = 0; while (!q.empty()) { int sz = q.size(); ++sem; taken += sz; for (int i = 0; i < sz; ++i) { int u = q.front(); q.pop(); for (int v : adj[u]) if (--indeg[v] == 0) q.push(v); } } return taken == n ? sem : -1; }};func minimumSemesters(n int, relations [][]int) int { adj := make([][]int, n+1) indeg := make([]int, n+1) for _, r := range relations { adj[r[0]] = append(adj[r[0]], r[1]) indeg[r[1]]++ } q := []int{} for i := 1; i <= n; i++ { if indeg[i] == 0 { q = append(q, i) } } sem, taken := 0, 0 for len(q) > 0 { sz := len(q) sem++ taken += sz for i := 0; i < sz; i++ { u := q[0] q = q[1:] for _, v := range adj[u] { indeg[v]-- if indeg[v] == 0 { q = append(q, v) } } } } if taken == n { return sem } return -1}from collections import deque
class Solution: def minimumSemesters(self, n: int, relations: List[List[int]]) -> int: adj = [[] for _ in range(n + 1)] indeg = [0] * (n + 1) for a, b in relations: adj[a].append(b) indeg[b] += 1 q = deque(i for i in range(1, n + 1) if indeg[i] == 0) sem = 0 taken = 0 while q: sz = len(q) sem += 1 taken += sz for _ in range(sz): u = q.popleft() for v in adj[u]: indeg[v] -= 1 if indeg[v] == 0: q.append(v) return sem if taken == n else -1function minimumSemesters(n, relations) { const adj = Array.from({ length: n + 1 }, () => []); const indeg = new Array(n + 1).fill(0); for (const [a, b] of relations) { adj[a].push(b); indeg[b]++; } let q = []; for (let i = 1; i <= n; i++) if (indeg[i] === 0) q.push(i); let sem = 0, taken = 0; while (q.length > 0) { sem++; taken += q.length; const next = []; for (const u of q) { for (const v of adj[u]) { if (--indeg[v] === 0) next.push(v); } } q = next; } return taken === n ? sem : -1;}class Solution { public int minimumSemesters(int n, int[][] relations) { List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i <= n; i++) adj.add(new ArrayList<>()); int[] indeg = new int[n + 1]; for (int[] r : relations) { adj.get(r[0]).add(r[1]); indeg[r[1]]++; } Deque<Integer> q = new ArrayDeque<>(); for (int i = 1; i <= n; i++) if (indeg[i] == 0) q.offer(i); int sem = 0, taken = 0; while (!q.isEmpty()) { int sz = q.size(); sem++; taken += sz; for (int i = 0; i < sz; i++) { int u = q.poll(); for (int v : adj.get(u)) { if (--indeg[v] == 0) q.offer(v); } } } return taken == n ? sem : -1; }}function minimumSemesters(n: number, relations: number[][]): number { const adj: number[][] = Array.from({ length: n + 1 }, () => []); const indeg: number[] = new Array(n + 1).fill(0); for (const [a, b] of relations) { adj[a].push(b); indeg[b]++; } let q: number[] = []; for (let i = 1; i <= n; i++) if (indeg[i] === 0) q.push(i); let sem = 0, taken = 0; while (q.length > 0) { sem++; taken += q.length; const next: number[] = []; for (const u of q) { for (const v of adj[u]) { if (--indeg[v] === 0) next.push(v); } } q = next; } return taken === n ? sem : -1;}Editorial#
The “layered” BFS processes one level (semester) at a time. The size of the queue at each iteration’s start is the semester’s batch.
Complexity#
- Time: O(V + E).
- Space: O(V + E).
Concept revision#
Related#