Can every course be finished?
There are n courses, numbered 0 to n − 1. Each pair [a, b] in
prereqs means course b must be taken before course a. Return true if it is possible to
take every course, and false otherwise.
Examples
| Input | Output | Why |
|---|---|---|
n = 4, prereqs = [[1, 0], [2, 1], [3, 2]] | true | A chain: take 0, 1, 2, 3 in order. |
n = 3, prereqs = [[1, 0], [2, 1], [0, 2]] | false | 0 before 1 before 2 before 0: a cycle, so none of them can start. |
n = 5, prereqs = [[1, 0], [2, 0], [4, 3]] | true | Two separate groups, both without cycles. |
Constraints
- 1 ≤ n ≤ 10⁵
- 1 ≤ len(prereqs) ≤ 2 × 10⁵
- 0 ≤ a, b < n; pairs may repeat
Hints
Hint 1
Draw an edge b → a for each pair. When can a course be taken right now?
Hint 2
A course can be taken when all its prerequisites are done: when its count of unfinished prerequisites (its in-degree) is zero.
Hint 3
Queue every course with in-degree 0. Taking one lowers the in-degree of the courses it unlocks; queue any that reach 0. Every course gets taken exactly when there is no cycle.
Where people lose marks
- Reversing the edge direction. It does not change the yes/no answer, but it gives the order backwards if you are asked for it.
- Recursive depth-first search for cycle detection with only two states. You need three (unvisited, on the current path, finished), or a chain looks like a cycle.
- Recursion depth: a chain of 10⁵ courses overflows the stack in a recursive solution. Kahn's algorithm is iterative.
- Forgetting courses that appear in no pair: they have in-degree 0 and must be counted as taken.
Dry run on 5,[[1,0],[2,0],[3,1],[3,2],[4,3]]
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 / 7in-degree0011122314queue00taken0
Count prerequisites. Course 0 needs none: queue it.
-
Step 2 / 7in-degree0001022314queue1021taken00taken1
Take course 0. It unlocks 1, 2; 1, 2 now have no prerequisites left and join the queue.
-
Step 3 / 7in-degree0001021314queue20taken0011taken2
Take course 1. It unlocks 3, which still wait for others.
-
Step 4 / 7in-degree0001020314queue30taken001122taken3
Take course 2. It unlocks 3; 3 now has no prerequisites left and joins the queue.
-
Step 5 / 7in-degree0001020304queue40taken00112233taken4
Take course 3. It unlocks 4; 4 now has no prerequisites left and joins the queue.
-
Step 6 / 7in-degree0001020304queueempty0taken0011223344taken5
Take course 4. Nothing depends on it.
-
Step 7 / 7taken0011223344answertrue
All 5 courses taken, in the order 0, 1, 2, 3, 4. No cycle: true.
How to think about it
The pairs define a directed graph with an edge from each prerequisite to the course that needs it. All the courses can be taken in some order exactly when the graph has a topological order, which happens exactly when it has no directed cycle.
Kahn's algorithm builds the order directly:
- Count each course's prerequisites (its in-degree) and queue the courses with none.
- Take a course from the queue; for each course it unlocks, subtract one from that course's count, and queue it when the count reaches zero.
- If every course was taken, the answer is true. Courses on a cycle never reach zero, because each waits for another member of the cycle.
Duplicate pairs are harmless: the same edge adds one to the count twice and removes it twice.
Complexity. Time O(n + len(prereqs)) · Space O(n + len(prereqs))
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import deque
def can_finish(n, prereqs):
unlocks = [[] for _ in range(n)]
indeg = [0] * n # prerequisites still to take
for a, b in prereqs:
unlocks[b].append(a)
indeg[a] += 1
q = deque(i for i in range(n) if indeg[i] == 0)
taken = 0
while q:
c = q.popleft()
taken += 1
for a in unlocks[c]:
indeg[a] -= 1
if indeg[a] == 0: # its last prerequisite is done
q.append(a)
return taken == n # a cycle leaves its courses waiting for ever#include <vector>
bool solve(int n, const std::vector<std::vector<int>>& prereqs) {
std::vector<std::vector<int>> unlocks(n);
std::vector<int> indeg(n, 0); // prerequisites still to take
for (const auto& p : prereqs) { unlocks[p[1]].push_back(p[0]); ++indeg[p[0]]; }
std::vector<int> q;
for (int i = 0; i < n; ++i) if (indeg[i] == 0) q.push_back(i);
for (size_t head = 0; head < q.size(); ++head)
for (int a : unlocks[q[head]])
if (--indeg[a] == 0) q.push_back(a); // its last prerequisite is done
return (int)q.size() == n; // every course was taken
}class Solution {
public boolean canFinish(int n, int[][] prereqs) {
java.util.List<java.util.List<Integer>> unlocks = new java.util.ArrayList<>();
for (int i = 0; i < n; i++) unlocks.add(new java.util.ArrayList<>());
int[] indeg = new int[n]; // prerequisites still to take
for (int[] p : prereqs) { unlocks.get(p[1]).add(p[0]); indeg[p[0]]++; }
int[] q = new int[n];
int tail = 0;
for (int i = 0; i < n; i++) if (indeg[i] == 0) q[tail++] = i;
for (int head = 0; head < tail; head++)
for (int a : unlocks.get(q[head]))
if (--indeg[a] == 0) q[tail++] = a; // its last prerequisite is done
return tail == n; // every course was taken
}
}function canFinish(n, prereqs) {
const unlocks = Array.from({ length: n }, () => []);
const indeg = new Array(n).fill(0); // prerequisites still to take
for (const [a, b] of prereqs) { unlocks[b].push(a); indeg[a]++; }
const q = [];
for (let i = 0; i < n; i++) if (indeg[i] === 0) q.push(i);
let head = 0;
while (head < q.length) {
const c = q[head++];
for (const a of unlocks[c]) if (--indeg[a] === 0) q.push(a);
}
return head === n; // every course was taken
}fn solve(n: i64, prereqs: &[[i64; 2]]) -> bool {
let n = n as usize;
let mut unlocks = vec![Vec::new(); n];
let mut indeg = vec![0usize; n]; // prerequisites still to take
for p in prereqs {
unlocks[p[1] as usize].push(p[0] as usize);
indeg[p[0] as usize] += 1;
}
let mut q: Vec<usize> = (0..n).filter(|&i| indeg[i] == 0).collect();
let mut head = 0;
while head < q.len() {
let c = q[head];
head += 1;
for &a in &unlocks[c] {
indeg[a] -= 1;
if indeg[a] == 0 {
q.push(a); // its last prerequisite is done
}
}
}
q.len() == n // every course was taken
}import Data.List (nub)
import qualified Data.Map.Strict as M
-- Kahn's algorithm: repeatedly take a course with no prerequisites left.
solve :: Int -> [[Int]] -> Bool
solve n prereqs = go [i | i <- [0 .. n - 1], M.findWithDefault 0 i indeg0 == 0] indeg0 0
where
unlocks = M.fromListWith (++) [(b, [a]) | [a, b] <- prereqs]
indeg0 = M.fromListWith (+) [(a, 1 :: Int) | [a, _] <- prereqs]
go [] _ taken = taken == n
go (c : rest) indeg taken =
let nexts = M.findWithDefault [] c unlocks
indeg' = foldr (M.adjust (subtract 1)) indeg nexts
freed = nub [a | a <- nexts, M.findWithDefault 0 a indeg' == 0, M.findWithDefault 0 a indeg > 0]
in go (rest ++ freed) indeg' (taken + 1)
Variants to try
- Return an order in which the courses can be taken, or an empty list if there is none.
- Each course takes one term and any number can run in parallel: how many terms are needed? (The longest path.)
- Find and print one cycle when the answer is false.
- The same algorithm schedules build targets, spreadsheet cells and database migrations. Name what changes in each.
Next problems
An order to take every course
Return the smallest order in which every course can be taken, or an empty list if prerequisites form a cycle.
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
An order to take every course
Return the smallest order in which every course can be taken, or an empty list if prerequisites form a cycle.
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.