An order to take every course
There are n courses numbered 0 to n − 1. Each pair
[a, b] in prereqs means course b must be taken before course
a. One course is taken at a time.
Return an order in which all n courses can be taken. Many orders may be valid: return the one that
is smallest in dictionary order, that is, at every step take the smallest-numbered course whose prerequisites are
all done. If no order exists, return an empty list.
Examples
| Input | Output | Why |
|---|---|---|
n = 4, prereqs = [[1,0],[2,0],[3,1],[3,2]] | [0, 1, 2, 3] | 0 first; then 1 and 2 are both available and 1 is smaller; 3 needs both. |
n = 2, prereqs = [[1,0],[0,1]] | [] | Each course needs the other. |
n = 4, prereqs = [[0,1],[1,2],[2,3]] | [3, 2, 1, 0] | A chain run backwards: 3 has no prerequisite. |
Constraints
- 1 ≤ n ≤ 2000
- 0 ≤ prereqs.length ≤ 5000
- a ≠ b; no pair is repeated
Hints
Hint 1
Draw an edge b → a for each pair [a, b]. A course can be taken when its in-degree, counting only courses not yet taken, is zero.
Hint 2
Kahn's algorithm takes any course with in-degree zero. To get the smallest order, always take the smallest one: keep the available courses in a min-heap.
Hint 3
If the heap empties before every course is taken, the remaining courses are on or behind a cycle: return [].
Where people lose marks
- Building the edge a → b instead of b → a, which produces a reversed order.
- Using a plain queue and returning a valid but not the smallest order.
- Returning the partial order when a cycle stops the algorithm, instead of an empty list.
Dry run on 7,[[2,0],[3,0],[3,1],[4,2],[4,3],[5,3],[6,4],[6,5]]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 8ready (in-degree 0)0011order—0
Count incoming edges. 0, 1 have none, so they are ready.
-
Step 2 / 8ready (in-degree 0)1021order00
Take 0, the smallest ready node, and remove its outgoing edges. 2 now has no incoming edge and is ready.
-
Step 3 / 8ready (in-degree 0)2031order0011
Take 1, the smallest ready node, and remove its outgoing edges. 3 now has no incoming edge and is ready.
-
Step 4 / 8ready (in-degree 0)30order001122
Take 2, the smallest ready node, and remove its outgoing edges.
-
Step 5 / 8ready (in-degree 0)4051order00112233
Take 3, the smallest ready node, and remove its outgoing edges. 4, 5 now have no incoming edge and are ready.
-
Step 6 / 8ready (in-degree 0)50order0011223344
Take 4, the smallest ready node, and remove its outgoing edges.
-
Step 7 / 8ready (in-degree 0)60order001122334455
Take 5, the smallest ready node, and remove its outgoing edges. 6 now has no incoming edge and is ready.
-
Step 8 / 8ready (in-degree 0)empty0order00112233445566
Take 6, the smallest ready node, and remove its outgoing edges. Every node is placed: 0, 1, 2, 3, 4, 5, 6 respects every edge.
How to think about it
This is Kahn's algorithm with one change. Count each course's prerequisites; the courses with none are available. Repeatedly take an available course, append it to the order, and decrement the count of every course that depends on it, making any that reach zero available.
Any available course gives a valid order. To get the smallest in dictionary order, the choice at each step must be the smallest available course, and an exchange argument shows that greedy choice is optimal: the first position where two valid orders differ is decided by which course goes there, and the smallest available one is the best any valid order can do. A min-heap gives that course in O(log n).
Courses on a cycle never reach zero, so the heap runs dry early; an order shorter than n means "impossible".
Complexity. Time O(p + n log n) · Space O(n + p)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
import heapq
def course_order(n, prereqs):
after = [[] for _ in range(n)] # b -> courses that need b
indeg = [0] * n
for a, b in prereqs:
after[b].append(a)
indeg[a] += 1
ready = [c for c in range(n) if indeg[c] == 0]
heapq.heapify(ready) # smallest available course first
order = []
while ready:
c = heapq.heappop(ready)
order.append(c)
for nxt in after[c]:
indeg[nxt] -= 1
if indeg[nxt] == 0:
heapq.heappush(ready, nxt)
return order if len(order) == n else [] # short: a cycle blocked the rest#include <functional>
#include <queue>
#include <vector>
std::vector<int> solve(int n, const std::vector<std::vector<int>>& prereqs) {
std::vector<std::vector<int>> after(n);
std::vector<int> indeg(n, 0);
for (const auto& p : prereqs) {
after[p[1]].push_back(p[0]);
++indeg[p[0]];
}
std::priority_queue<int, std::vector<int>, std::greater<int>> ready; // min-heap
for (int c = 0; c < n; ++c) if (indeg[c] == 0) ready.push(c);
std::vector<int> order;
while (!ready.empty()) {
int c = ready.top();
ready.pop();
order.push_back(c);
for (int nxt : after[c]) if (--indeg[nxt] == 0) ready.push(nxt);
}
if ((int)order.size() < n) order.clear(); // a cycle blocked the rest
return order;
}import java.util.*;
class Solution {
public int[] courseOrder(int n, int[][] prereqs) {
List<List<Integer>> after = new ArrayList<>();
for (int i = 0; i < n; i++) after.add(new ArrayList<>());
int[] indeg = new int[n];
for (int[] p : prereqs) {
after.get(p[1]).add(p[0]);
indeg[p[0]]++;
}
PriorityQueue<Integer> ready = new PriorityQueue<>(); // smallest first
for (int c = 0; c < n; c++) if (indeg[c] == 0) ready.add(c);
int[] order = new int[n];
int k = 0;
while (!ready.isEmpty()) {
int c = ready.poll();
order[k++] = c;
for (int nxt : after.get(c)) if (--indeg[nxt] == 0) ready.add(nxt);
}
return k == n ? order : new int[0]; // short: a cycle blocked the rest
}
}function courseOrder(n, prereqs) {
const after = Array.from({ length: n }, () => []);
const indeg = new Array(n).fill(0);
for (const [a, b] of prereqs) { after[b].push(a); indeg[a]++; }
// a binary min-heap of available courses
const heap = [];
const push = x => { heap.push(x); let i = heap.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (heap[p] <= heap[i]) break; [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; } };
const pop = () => { const top = heap[0], last = heap.pop(); if (heap.length) { heap[0] = last; let i = 0; for (;;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < heap.length && heap[l] < heap[m]) m = l; if (r < heap.length && heap[r] < heap[m]) m = r; if (m === i) break; [heap[m], heap[i]] = [heap[i], heap[m]]; i = m; } } return top; };
for (let c = 0; c < n; c++) if (indeg[c] === 0) push(c);
const order = [];
while (heap.length) {
const c = pop();
order.push(c);
for (const nxt of after[c]) if (--indeg[nxt] === 0) push(nxt);
}
return order.length === n ? order : []; // short: a cycle blocked the rest
}use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn solve(n: i64, prereqs: &[[i64; 2]]) -> Vec<i64> {
let n = n as usize;
let mut after = vec![Vec::new(); n];
let mut indeg = vec![0usize; n];
for p in prereqs {
after[p[1] as usize].push(p[0] as usize);
indeg[p[0] as usize] += 1;
}
let mut ready: BinaryHeap<Reverse<usize>> = (0..n).filter(|&c| indeg[c] == 0).map(Reverse).collect();
let mut order = Vec::with_capacity(n);
while let Some(Reverse(c)) = ready.pop() {
order.push(c as i64);
for &nxt in &after[c] {
indeg[nxt] -= 1;
if indeg[nxt] == 0 {
ready.push(Reverse(nxt));
}
}
}
if order.len() == n { order } else { Vec::new() } // short: a cycle blocked the rest
}import Data.List (foldl')
import qualified Data.Map.Strict as M
import qualified Data.Set as S
solve :: Int -> [[Int]] -> [Int]
solve n prereqs = go ready0 indeg0 []
where
after = M.fromListWith (++) [(b, [a]) | [a, b] <- prereqs]
indeg0 = M.fromListWith (+) [(a, 1 :: Int) | [a, _] <- prereqs]
ready0 = S.fromList [c | c <- [0 .. n - 1], M.findWithDefault 0 c indeg0 == 0]
-- the set is a min-heap: its smallest element is the next course
go ready indeg acc = case S.minView ready of
Nothing -> if length acc == n then reverse acc else []
Just (c, rest) ->
let (ready', indeg') = foldl' release (rest, indeg) (M.findWithDefault [] c after)
in go ready' indeg' (c : acc)
release (r, d) nxt =
let k = M.findWithDefault 0 nxt d - 1
in (if k == 0 then S.insert nxt r else r, M.insert nxt k d)
Variants to try
- Courses can run in parallel, one term each: return the fewest terms needed (process the ready set round by round).
- Return the number of distinct valid orders for n ≤ 15 (dynamic programming over subsets of courses taken).
- Alien dictionary: derive the prerequisite edges from a sorted list of words, then sort.
Next problems
Can every course be finished?
Dependencies form a directed graph, satisfiable exactly when it has no cycle. Kahn's algorithm takes courses with no prerequisites left until it runs out.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem →Write a solution and run it against the real test table.
Keep going
Can every course be finished?
Dependencies form a directed graph, satisfiable exactly when it has no cycle. Kahn's algorithm takes courses with no prerequisites left until it runs out.
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.