190. Course Schedule II
๐ LeetCode Problem: 210. Course Schedule II
๐ Difficulty: Medium
๐ท๏ธ Topics: Graph, DFS, BFS, Topological Sort
Problem Statement
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] = [aแตข, bแตข] indicates that you must take course bแตข first if you want to take course aแตข.
- For example, the pair
[0, 1]indicates that to take course0you have to first take course1.
Return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array.
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: [0,1]
Explanation:
Total 2 courses. To take course 1, need course 0 first.
Valid order: [0, 1] โ
Example 2:
Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: [0,2,1,3]
Explanation:
Multiple valid orders exist:
- [0,1,2,3]
- [0,2,1,3] โ
(one valid answer)
Both are correct!
Example 3:
Input: numCourses = 1, prerequisites = []
Output: [0]
Explanation:
Only one course, no prerequisites.
Just take it! โ
Constraints:
- 1 <= numCourses <= 2000
- 0 <= prerequisites.length <= numCourses * (numCourses - 1)
- prerequisites[i].length == 2
- 0 <= aแตข, bแตข < numCourses
- aแตข != bแตข
- All the pairs [aแตข, bแตข] are distinct.
๐ณ Visual Understanding - The Complete Picture
The Problem We're Solving ๐ค
Same as Course Schedule, but now RETURN THE ORDER! ๐
Example:
numCourses = 4
prerequisites = [[1,0], [2,0], [3,1], [3,2]]
Graph:
0
/ \
โ โ
1 2
\ /
โ
3
Course Schedule asked: "Can we finish?" โ
/โ
Course Schedule II asks: "WHAT ORDER?" ๐
Valid ordering: [0, 1, 2, 3] or [0, 2, 1, 3]
Both respect all prerequisites! โ
Building Understanding Through Examples ๐
Example 1: Simple chain
prerequisites = [[1,0]]
Graph:
0 โ 1
Order: [0, 1] โ
Step 1: Take 0 (no prerequisites)
Step 2: Take 1 (prerequisite 0 completed)
Simple and clear! ๐ฏ
Example 2: Diamond graph
prerequisites = [[1,0], [2,0], [3,1], [3,2]]
Graph:
0
/ \
1 2
\ /
3
Multiple valid orders:
[0, 1, 2, 3] โ
Take 1 before 2
[0, 2, 1, 3] โ
Take 2 before 1
Both valid because:
- 0 has no prerequisites (take first)
- 1 and 2 both need 0 (can do in any order)
- 3 needs both 1 and 2 (take last)
Return ANY valid order! ๐
Example 3: Impossible (cycle)
prerequisites = [[1,0], [0,1]]
Graph:
0 โ 1 (cycle!)
No valid order exists! โ
Return: [] (empty array)
Same as Course Schedule returning false!
Visual: Topological Order
Graph:
0
/ \
1 2
\ /
3
Level-by-level view:
Level 0: {0} - no dependencies
Level 1: {1, 2} - depend on level 0
Level 2: {3} - depends on level 1
One valid order: Process level by level!
[0, 1, 2, 3] or [0, 2, 1, 3] โ
This is TOPOLOGICAL ORDERING! ๐ฏ
The Pattern Emerges ๐ก
Key observations: ๐
1๏ธโฃ Extension of Course Schedule ๐
Same cycle detection
PLUS: collect the ordering!
2๏ธโฃ Topological Sort Output ๐
Course Schedule: boolean
Course Schedule II: actual order
3๏ธโฃ Two Approaches Still Work ๐ ๏ธ
DFS: Post-order collection
BFS (Kahn's): Processing order IS the answer!
4๏ธโฃ Return Empty on Cycle ๐
If cycle detected: return []
If DAG: return valid order
5๏ธโฃ Multiple Valid Answers โจ
Any topological order works!
Just return ONE valid order
๐ง The Journey From Brute Force to Optimal
Part 1: The Connection - "Building on Course Schedule!" ๐ค
๐ญ We already solved the hard part!
Course Schedule (Problem 189):
โ
Build graph
โ
Detect cycles
โ
Return true/false
Course Schedule II:
โ
Build graph (same!)
โ
Detect cycles (same!)
๐ COLLECT the ordering!
๐ Return order or []
The NEW part: Collecting the order! ๐
Two approaches from before:
1. DFS with three states
2. BFS (Kahn's algorithm)
Both need modification to collect order! ๐ง
Part 2: The DFS Approach - "Post-order is topological!" ๐ญ
๐ค DFS insight: Post-order traversal gives topological order!
Why post-order?
When we finish exploring a node:
- All its dependencies explored
- All courses it depends on done
- Safe to add to order!
Example:
Graph: 0 โ 1 โ 2
DFS from 0:
Explore 0 โ 1 โ 2
Finish 2 first (no dependencies)
Finish 1 next (2 done)
Finish 0 last (1 done)
Post-order: [2, 1, 0]
Reverse: [0, 1, 2] โ
Valid topological order!
Key modification:
When marking node VISITED:
Add to result list!
After DFS completes:
Reverse the list!
This gives topological order! ๐ฏ
Algorithm:
List<Integer> order = new ArrayList<>();
void dfs(node):
mark VISITING
for each neighbor:
if VISITING: return false (cycle)
if UNVISITED: dfs(neighbor)
mark VISITED
order.add(node) // Add in post-order! ๐
return true
Collections.reverse(order) // Reverse for correct order!
return order
Simple extension of Course Schedule! โจ
Part 3: The BFS Approach - "Processing order IS the answer!" ๐ก
๐ค BFS (Kahn's) insight: Even simpler!
The order we PROCESS courses = valid topological order! ๐ฏ
Why?
Kahn's algorithm processes in topological order:
1. Start with zero in-degree (no dependencies)
2. Process them
3. Remove edges, find new zero in-degree
4. Repeat
The processing order respects all dependencies! โ
Example:
Graph: 0 โ 1 โ 2
In-degrees: [0: 0, 1: 1, 2: 1]
Step 1: Process 0 (in-degree 0)
order = [0]
Update in-degrees: [1: 0, 2: 1]
Step 2: Process 1 (in-degree 0)
order = [0, 1]
Update in-degrees: [2: 0]
Step 3: Process 2 (in-degree 0)
order = [0, 1, 2] โ
This IS the topological order! No reversal needed!
Key modification:
int[] order = new int[numCourses];
int index = 0;
while (queue not empty):
course = queue.poll()
order[index++] = course // Add to result! ๐
// Update in-degrees as before...
if (index == numCourses):
return order // All processed! โ
else:
return new int[0] // Cycle! โ
Even simpler than DFS! No reversal needed! ๐
Part 4: The Comparison - "Which to use?" ๐ก
๐ค DFS vs BFS for Course Schedule II:
DFS Approach:
โ
Natural cycle detection
โ
Familiar recursion
โ Need to reverse list
โ Post-order concept might confuse
Result collection:
Add in post-order โ reverse
BFS Approach (Kahn's):
โ
Processing order IS answer (no reverse!)
โ
Clearer logic
โ
Iterative (no stack overflow)
โ
More intuitive
Result collection:
Add while processing โ done!
For Course Schedule II:
BFS is CLEANER! ๐ฏ
Processing order naturally gives topological sort!
No reversal, no post-order confusion!
Recommendation: Use BFS (Kahn's)! โญ
Both work and both O(V + E), but BFS is cleaner
for problems that need the actual ordering! ๐
Part 5: The Pattern - "Topological sort output!" ๐ฏ
โจ This solidifies the topological sort pattern!
Course Schedule (189):
Input: Graph (prerequisites)
Output: Boolean (possible?)
Method: Cycle detection
Course Schedule II (190): โ YOU ARE HERE
Input: Graph (prerequisites)
Output: Ordering (what order?)
Method: Topological sort with output
Pattern evolution:
Level 1: Detect if ordering exists
Level 2: Return the actual ordering
Many problems follow this pattern:
- Alien Dictionary: Return alphabet order
- Sequence Reconstruction: Verify unique order
- Parallel Courses: Track levels/time
Template for "return ordering" problems:
BFS Template:
Calculate in-degrees
Queue up zero in-degree nodes
List<Integer> result = new ArrayList<>();
while (queue not empty):
node = queue.poll()
result.add(node) // Collect order! ๐
Update neighbors' in-degrees
Add newly-zero to queue
return result.size() == n ? result : empty
DFS Template:
List<Integer> result = new ArrayList<>();
For each unvisited:
if (hasCycle): return empty
// In DFS:
When finishing node:
result.add(node)
Collections.reverse(result)
return result
Pattern mastered! ๐
๐ฏ Approach 1: BFS (Kahn's Algorithm) - Recommended โญ
The Key Insight ๐ก
Use Kahn's algorithm where processing order IS topological order! ๐ Calculate in-degrees, queue zero in-degree courses ๐. Process courses: add to result, decrease neighbors' in-degrees, queue newly-zero โ . If processed all courses, return order ๐. Otherwise return empty array (cycle) ๐. No reversal needed - processing order is answer! Time: O(V+E) โก, Space: O(V+E) ๐ฆ!
Cleanest approach! โจ
Complete Implementation
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// KAHN'S ALGORITHM - CLEANEST FOR THIS PROBLEM! โญ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Processing order IS topological order
// No reversal needed!
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// BUILD ADJACENCY LIST ๐๏ธ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// CALCULATE IN-DEGREES ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int[] inDegree = new int[numCourses];
for (int[] prereq : prerequisites) {
int course = prereq[0];
int prerequisite = prereq[1];
graph.get(prerequisite).add(course); // prereq โ course
inDegree[course]++; // One more prerequisite
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// QUEUE COURSES WITH NO PREREQUISITES ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Queue<Integer> queue = new LinkedList<>();
for (int course = 0; course < numCourses; course++) {
if (inDegree[course] == 0) {
queue.offer(course); // Can take immediately! โ
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// RESULT ARRAY ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int[] order = new int[numCourses];
int index = 0;
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// BFS: PROCESS COURSES IN TOPOLOGICAL ORDER ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
while (!queue.isEmpty()) {
int course = queue.poll();
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ADD TO RESULT ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// This course can be taken now!
// Add to ordering!
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
order[index++] = course;
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// DECREASE IN-DEGREE OF DEPENDENT COURSES ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
for (int dependent : graph.get(course)) {
inDegree[dependent]--;
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// IF NOW READY, ADD TO QUEUE ๐ฏ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (inDegree[dependent] == 0) {
queue.offer(dependent);
}
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// CHECK IF ALL COURSES PROCESSED โ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// If processed all: return order โ
// If some remain: cycle exists, return empty โ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if (index == numCourses) {
return order; // Success! ๐
} else {
return new int[0]; // Cycle detected! ๐
}
}
}
Why This Works ๐ฏ
Processing order = topological order: ๐ - Kahn's processes in dependency order - Zero in-degree โ no unmet dependencies - Safe to "take" course now! - Order respects all prerequisites โ
No reversal needed: โก - Unlike DFS which gives post-order - BFS gives correct order directly! - Cleaner and more intuitive!
Cycle detection built-in: ๐ - If cycle: some courses never reach zero in-degree - index < numCourses at end - Return empty array!
๐ฏ Approach 2: DFS with Post-order Collection
The Key Insight ๐ก
Use DFS with post-order collection ๐. Add course to result when marking VISITED (all dependencies done) โ . After all DFS, reverse the result ๐. Reversed post-order = topological order! ๐ If cycle detected, return empty array โ. Time: O(V+E) โก, Space: O(V+E) ๐ฆ!
Classic approach! โจ
Complete Implementation
class Solution {
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// THREE STATES FOR CYCLE DETECTION ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
private static final int UNVISITED = 0;
private static final int VISITING = 1;
private static final int VISITED = 2;
public int[] findOrder(int numCourses, int[][] prerequisites) {
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// DFS POST-ORDER COLLECTION APPROACH ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// BUILD ADJACENCY LIST ๐๏ธ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int[] prereq : prerequisites) {
int course = prereq[0];
int prerequisite = prereq[1];
graph.get(prerequisite).add(course);
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// STATE TRACKING & RESULT LIST ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int[] state = new int[numCourses];
List<Integer> order = new ArrayList<>();
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// DFS FROM EACH UNVISITED COURSE ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
for (int course = 0; course < numCourses; course++) {
if (state[course] == UNVISITED) {
if (hasCycle(graph, state, course, order)) {
return new int[0]; // Cycle! โ
}
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// REVERSE POST-ORDER TO GET TOPOLOGICAL ORDER ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Collections.reverse(order);
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// CONVERT TO ARRAY ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int[] result = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
result[i] = order.get(i);
}
return result;
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// DFS HELPER WITH POST-ORDER COLLECTION ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
private boolean hasCycle(List<List<Integer>> graph, int[] state,
int course, List<Integer> order) {
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// MARK AS VISITING ๐ก
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
state[course] = VISITING;
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// EXPLORE NEIGHBORS ๐งญ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
for (int neighbor : graph.get(course)) {
if (state[neighbor] == VISITING) {
return true; // Cycle! ๐ด
}
if (state[neighbor] == UNVISITED) {
if (hasCycle(graph, state, neighbor, order)) {
return true; // Cycle in subtree!
}
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// MARK AS VISITED & ADD TO POST-ORDER ๐
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// All dependencies of this course are done!
// Safe to add to ordering!
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
state[course] = VISITED;
order.add(course); // Post-order collection! ๐
return false; // No cycle!
}
}
Why This Works ๐ฏ
Post-order gives reverse topological order: ๐ - When course finishes: all dependencies done - Add to list at this point - Results in reverse order!
Reversal gives correct order: ๐ - Last finished = no dependencies โ should be first! - First finished = most dependencies โ should be last! - Reverse to get correct topological order!
Cycle detection same as before: โ - Three states work identically - VISITING โ VISITING = cycle - Return empty if cycle detected!
๐ Detailed Dry Run
Let's trace Approach 1 (BFS) on Example 2:
numCourses = 4
prerequisites = [[1,0], [2,0], [3,1], [3,2]]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SETUP โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Build graph from prerequisites:
[1,0]: 0 โ 1
[2,0]: 0 โ 2
[3,1]: 1 โ 3
[3,2]: 2 โ 3
Adjacency list:
0: [1, 2]
1: [3]
2: [3]
3: []
Graph visualization:
0
/ \
โ โ
1 2
\ /
โ
3
Calculate in-degrees:
Course 0: 0 prerequisites โ in-degree = 0
Course 1: 1 prerequisite (0) โ in-degree = 1
Course 2: 1 prerequisite (0) โ in-degree = 1
Course 3: 2 prerequisites (1,2) โ in-degree = 2
In-degree array: [0, 1, 1, 2]
BFS Processing ๐
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ INITIALIZATION โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ Find courses with in-degree 0: โ
โ Course 0: in-degree 0 โ
Add to queue โ
โ Course 1: in-degree 1 โ โ
โ Course 2: in-degree 1 โ โ
โ Course 3: in-degree 2 โ โ
โ โ
โ queue = [0] โ
โ order = [] โ
โ index = 0 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ ITERATION 1: Process course 0 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ Poll: course 0 โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ADD TO RESULT ๐ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ order[0] = 0 โ
โ order = [0, _, _, _] โ
โ index = 1 โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ UPDATE NEIGHBORS ๐ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Course 0's neighbors: [1, 2] โ
โ โ
โ Neighbor 1: โ
โ in-degree[1]-- โ 1-1 = 0 โ
โ
โ Now ready! Add to queue โ
โ โ
โ Neighbor 2: โ
โ in-degree[2]-- โ 1-1 = 0 โ
โ
โ Now ready! Add to queue โ
โ โ
โ queue = [1, 2] โ
โ in-degree = [0, 0, 0, 2] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ ITERATION 2: Process course 1 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ Poll: course 1 โ
โ โ
โ ADD TO RESULT ๐ โ
โ order[1] = 1 โ
โ order = [0, 1, _, _] โ
โ index = 2 โ
โ โ
โ UPDATE NEIGHBORS ๐ โ
โ Course 1's neighbors: [3] โ
โ โ
โ Neighbor 3: โ
โ in-degree[3]-- โ 2-1 = 1 โ
โ Still has dependency (course 2) โ
โ Don't add to queue yet โ
โ โ
โ queue = [2] โ
โ in-degree = [0, 0, 0, 1] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ ITERATION 3: Process course 2 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ Poll: course 2 โ
โ โ
โ ADD TO RESULT ๐ โ
โ order[2] = 2 โ
โ order = [0, 1, 2, _] โ
โ index = 3 โ
โ โ
โ UPDATE NEIGHBORS ๐ โ
โ Course 2's neighbors: [3] โ
โ โ
โ Neighbor 3: โ
โ in-degree[3]-- โ 1-1 = 0 โ
โ
โ Now ready! Add to queue โ
โ โ
โ queue = [3] โ
โ in-degree = [0, 0, 0, 0] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ ITERATION 4: Process course 3 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ Poll: course 3 โ
โ โ
โ ADD TO RESULT ๐ โ
โ order[3] = 3 โ
โ order = [0, 1, 2, 3] โ
โ
โ index = 4 โ
โ โ
โ UPDATE NEIGHBORS ๐ โ
โ Course 3's neighbors: [] (none) โ
โ No neighbors to update โ
โ โ
โ queue = [] (empty!) โ
โ โ
โ BFS Complete! โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
FINAL RESULT ๐
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ FINAL RESULT: [0,1,2,3] โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
โ โ
โ All courses processed! โ
โ
โ index = 4 = numCourses โ
โ
โ โ
โ Valid topological ordering: [0, 1, 2, 3] โ
โ โ
โ Verification: โ
โ Course 0: No prerequisites โ taken first โ
โ Course 1: Needs 0 โ 0 before 1 โ
โ
โ Course 2: Needs 0 โ 0 before 2 โ
โ
โ Course 3: Needs 1,2 โ 1,2 before 3 โ
โ
โ โ
โ All prerequisites satisfied! ๐ โ
โ โ
โ Note: [0,2,1,3] also valid! โ
โ (1 and 2 can swap - both depend only on 0) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Key Observations:
-
Processing order IS the answer: ๐ No reversal needed with BFS!
-
In-degrees track readiness: ๐ Zero in-degree = ready to take
-
Queue ensures correct order: ๐ Dependencies satisfied before processing
-
Index check detects cycles: ๐ index < numCourses would mean cycle
-
Clean and intuitive: โจ BFS is perfect for this problem!
๐ Complexity Analysis
Time Complexity: O(V + E) โก
Where:
V = numCourses (vertices)
E = prerequisites.length (edges)
Both approaches (DFS and BFS):
Build graph: O(E)
Process each prerequisite once
Approach 1 (BFS):
Calculate in-degrees: O(E)
BFS process each course once: O(V)
Process each edge once: O(E)
Total: O(V + E) โ
Approach 2 (DFS):
DFS visit each course once: O(V)
Explore each edge once: O(E)
Reverse list: O(V)
Total: O(V + E) โ
Both: O(V + E) - same as Course Schedule! โก
Example: 2000 courses, 5000 prerequisites
Time: 2000 + 5000 = 7000 operations
Very efficient! ๐
Space Complexity: O(V + E) ๐ฆ
Space usage:
1. Graph (adjacency list): O(V + E) ๐๏ธ
V lists, total E entries
2. Approach 1 (BFS):
- In-degree array: O(V)
- Queue: O(V) worst case
- Result array: O(V)
- Total: O(V + E) โ
3. Approach 2 (DFS):
- State array: O(V)
- Result list: O(V)
- Recursion stack: O(V) worst case
- Total: O(V + E) โ
Both: O(V + E) ๐ฆ
Same as Course Schedule!
Just added result array/list (O(V))
which doesn't change overall complexity!
Example: 2000 courses, 5000 prerequisites
Graph: ~7000 space
Additional: ~2000-4000 space
Total: ~11000 space units
Reasonable! โ
๐ Related Problems & Patterns
Core Pattern: Topological Sort with Output ๐
// Template for topological sort with ordering
class Solution {
// BFS approach (Kahn's) - RECOMMENDED for output
public int[] topologicalSortBFS(int n, int[][] edges) {
List<List<Integer>> graph = buildGraph(n, edges);
int[] inDegree = calculateInDegrees(n, edges);
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
int[] result = new int[n];
int index = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
result[index++] = node; // Collect order! ๐
for (int neighbor : graph.get(node)) {
if (--inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
return index == n ? result : new int[0];
}
// DFS approach - needs reversal
public int[] topologicalSortDFS(int n, int[][] edges) {
List<List<Integer>> graph = buildGraph(n, edges);
int[] state = new int[n];
List<Integer> order = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (state[i] == UNVISITED) {
if (hasCycle(graph, state, i, order)) {
return new int[0];
}
}
}
Collections.reverse(order); // Reverse post-order! ๐
return order.stream().mapToInt(Integer::intValue).toArray();
}
}
When to Use This Pattern ๐ฏ
โ
Need actual topological ordering (not just existence)
โ
Return one valid order (not all)
โ
Dependencies between items
โ
Cycle detection + ordering
โ
Prerequisite-based problems
Recognition triggers: ๐ - "Return the ordering" or "return order" ๐ - "One valid order" (any valid answer) โจ - Same as Course Schedule but need ordering ๐ - Topological sort with output ๐ฏ
Key insight: BFS (Kahn's) cleaner for output problems! ๐ก
Related LeetCode Problems ๐
Direct Applications:
-
Course Schedule (LC 207) - Medium ๐ โข What's similar: Same graph, same cycle detection ๐ โข What's different: Boolean output, not ordering โ /โ โข Key insight: Prerequisite problem! ๐ฏ
-
Alien Dictionary (LC 269) - Hard ๐ฝ โข What's similar: Topological sort with output ๐ โข What's different: Build graph from word comparison ๐ โข Key insight: Extract dependencies first! ๐ก
-
Parallel Courses II (LC 1494) - Hard ๐ โข What's similar: Course dependencies, topological sort ๐ โข What's different: Minimize semesters, k courses/semester ๐ โข Key insight: Topological levels + optimization! โก
-
Sequence Reconstruction (LC 444) - Medium ๐ข โข What's similar: Topological sort, unique ordering ๐ฏ โข What's different: Verify sequences give unique order โ โข Key insight: Check uniqueness during Kahn's! ๐
Pattern Progression: ๐
Level 1: Detect Ordering Exists โ
/โ
โโ Course Schedule (LC 207)
Level 2: Return One Valid Ordering ๐ โ HERE ๐ฏ
โโ Course Schedule II (LC 210)
โโ Alien Dictionary (LC 269)
โโ Minimum Height Trees (LC 310)
Level 3: Advanced Topological Sort ๐
โโ Parallel Courses II (LC 1494)
โโ Sequence Reconstruction (LC 444)
โโ Sort Items by Groups (LC 1203)
๐ Quick Revision Notes
๐ฏ Core Concept
Course Schedule II returns actual topological ordering using BFS (Kahn's) or DFS ๐. BFS is CLEANER: processing order IS answer (no reversal!) ๐. Calculate in-degrees, queue zero in-degree courses ๐. Process: add to result, decrease neighbors' in-degrees, queue newly-zero โ . If processed all, return order ๐. Otherwise empty array (cycle) ๐. DFS works but needs post-order collection + reversal ๐. Time: O(V+E) โก, Space: O(V+E) ๐ฆ. BFS recommended! โญ
โก Quick Implementation (BFS - Recommended)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<Integer> res = new ArrayList<>();
// Approach 1 - BFS (kahn)
res = bfs(numCourses, prerequisites, res); // GOTCHA
Collections.reverse(res);
// // Approach 2 - DFS (plus stack)
// if (dfs(numCourses, prerequisites, res)) { // GOTCHA
// return new int[0];
// }
return res.stream().mapToInt(i -> i).toArray();
}
private boolean dfs(int numCourses, int[][] prerequisites, List<Integer> res) {
// Step 1: create graph (list of list) - no indegrees
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < prerequisites.length; i++) {
graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
}
// State 2: Create and track status of nodes (0, 1, 2) with 0 being default.
// 0 - UNVISITED, 1 - VISITING and 2 - VISITED
int[] state = new int[numCourses];
// State 3: Loop every course and detch to find cycle.
// If loop found, return false. Else, return true.
for (int i = 0; i < numCourses; i++) {
if (state[i] == 0) {
if (dfsUtil(i, state, graph, res)) { // has cycle?
return true;
}
}
}
return false;
}
private boolean dfsUtil(int course, int[] state, ArrayList<ArrayList<Integer>> graph, List<Integer> res) {
// Step 4: change the state of course being visited or considered.
state[course] = 1;
for (int depCourse : graph.get(course)) {
// Step 5: check the state of dependent course.
// If cycle is there, it gets detected here.
if (state[depCourse] == 1) {
return true;
}
// Prevents infinite loop (acts as visited)
if (state[depCourse] == 0) {
// Else, do a DFS from here.
if (dfsUtil(depCourse, state, graph, res)) {
return true;
}
}
}
// Step 6: Mark the node as visited
state[course] = 2;
res.add(course); // course added to res
return false;
}
private List<Integer> bfs(int numCourses, int[][] prerequisites, List<Integer> res) {
// Step 1: create graph (list of list) and indegrees (array).
ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
int[] indegrees = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < prerequisites.length; i++) {
graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
indegrees[prerequisites[i][1]]++;
}
// Step 2: find courses with no dependencies (indegrees with 0 value)
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (indegrees[i] == 0) {
queue.offer(i);
}
}
// Step 3: explore queue
int coursesTaken = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
res.add(course); // course added to res
coursesTaken++; // course taken
for (int depCourse : graph.get(course)) {
indegrees[depCourse]--;
if (indegrees[depCourse] == 0) { // no dependancy anymore
queue.offer(depCourse);
}
}
}
return coursesTaken == numCourses ? res : new ArrayList<>();
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(Arrays.toString(s.findOrder(2, new int[][] { { 1, 0 } })));
System.out.println(Arrays.toString(s.findOrder(4, new int[][] { { 1, 0 }, { 2, 0 }, { 3, 1 }, { 3, 2 } })));
System.out.println(Arrays.toString(s.findOrder(1, new int[][] {})));
System.out.println(Arrays.toString(s.findOrder(3, new int[][] { { 0, 2 }, { 1, 2 }, { 2, 0 } })));
}
}
๐ Key Insights
BFS vs DFS for Ordering: ๐
BFS (Kahn's Algorithm): โญ RECOMMENDED
Processing order = topological order!
while (queue not empty):
course = queue.poll()
result[index++] = course // Add directly! โ
No reversal needed! ๐ฏ
Advantages:
โ
Cleaner code
โ
More intuitive
โ
No reversal step
โ
Direct ordering
DFS (Post-order):
Post-order = reverse topological order!
void dfs(node):
// ... explore neighbors ...
result.add(node) // Add in post-order
Collections.reverse(result) // Must reverse! ๐
Disadvantages:
โ Need reversal step
โ Less intuitive
โ Post-order concept confusing
For ordering problems: USE BFS! ๐ฏ
Why Processing Order Works: ๐ก
Kahn's algorithm processes in dependency order:
Step 1: Process nodes with in-degree 0
- These have NO dependencies
- Safe to process first! โ
Step 2: Remove processed nodes
- Decrease neighbors' in-degrees
- Find NEW zero in-degree nodes
Step 3: Process newly-ready nodes
- Their dependencies now satisfied
- Safe to process! โ
Step 4: Repeat until done
The order we process = valid topological order!
Example:
Graph: A โ B โ C
Round 1: Process A (in-degree 0)
order = [A]
B now in-degree 0
Round 2: Process B
order = [A, B]
C now in-degree 0
Round 3: Process C
order = [A, B, C] โ
Processing order respects AโBโC! ๐ฏ
Multiple Valid Orders: โจ
Many problems have multiple valid topological orders!
Example:
A
/ \
B C
\ /
D
Valid orders:
[A, B, C, D] โ
[A, C, B, D] โ
Why? B and C both depend only on A
Can take in either order!
The problem: "Return ANY valid order"
Don't need all orders
Don't need specific order
Just ONE that works! ๐ฏ
Our BFS returns one valid order
(Based on queue processing order)
That's perfect! โ
Empty Array for Cycles: ๐
Key difference from Course Schedule:
Course Schedule: return false (boolean)
Course Schedule II: return [] (empty array)
Why empty array signals cycle:
If cycle exists:
Some courses never reach in-degree 0
Never added to queue
Never processed
index < numCourses
Return new int[0] (empty array)
Clean way to signal "impossible"! ๐
Check at end:
return index == numCourses ? order : new int[0];
Simple and elegant! โจ
๐ช Memory Aid
"BFS gives order as we go" ๐
"No reversal needed, smooth flow" โก
"DFS collects then must reverse the show" ๐
"For ordering problems, BFS is pro" ๐ โจ
โ ๏ธ Don't Forget
- Use BFS for ordering - cleaner than DFS! โญ
- Processing order IS answer - no reversal needed! ๐
- Add to result while processing - not after! ๐
- Check index == numCourses - detects cycles! ๐
- Return empty array on cycle - not null! ๐
- Multiple valid orders exist - return any one! โจ
You've mastered Course Schedule II - topological sort with output! ๐๐โจ