Course Schedule II
Problem
Given the number of courses and a list of prerequisite pairs, return a valid order in which to take all courses such that all prerequisites are satisfied, or return an empty list if no such order exists.
- 1 ≤ numCourses ≤ 2000
- 0 ≤ prerequisites.length ≤ numCourses * (numCourses - 1)
- prerequisites[i].length == 2
- 0 ≤ ai, bi < numCourses
- All the pairs prerequisites[i] are unique.
Example
numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]][0,2,1,3]The algorithm builds a graph where each course points to its prerequisites. It performs a DFS on each course, tracking the recursion stack to detect cycles. For course 3, it recursively visits prerequisites 1 and 2, which themselves depend on 0. The DFS postorder appends courses after all their prerequisites are visited, resulting in a valid order such as [0,2,1,3]. If a cycle is detected, the function returns an empty list.
Approach
Straightforward Solution
A brute-force approach might try all permutations of courses and check if prerequisites are satisfied, which is factorial time and infeasible for large inputs.
Core Observation
The problem reduces to finding a topological ordering of a directed graph where nodes are courses and edges represent prerequisites. A valid ordering exists if and only if the graph is a Directed Acyclic Graph (DAG). Detecting cycles is essential to determine feasibility.
Path to Optimal
PreviewRecognizing the problem as topological sorting on a graph, the key insight is to use DFS with cycle detection. By tracking nodes currently in the recursion stack, the algorithm detects cycles efficiently…
Full step-by-step walkthrough on Pro →
Optimal Approach
PreviewBuild an adjacency list mapping each course to its prerequisites. For each course, perform a DFS that returns False if a cycle is detected…
Full step-by-step walkthrough on Pro →
Want the full reasoning chain?
Unlock the complete walkthrough, line-by-line analysis, and recall drill.
Unlock ProTime
O(V + E)
Each course (vertex) and each prerequisite pair (edge) is visited once during DFS. Cycle detection and adjacency list traversal are linear in the size of the graph.
Space
O(V + E)
The adjacency list requires O(V + E) space. The recursion stack and visited sets require O(V) space. The output list stores all courses, also O(V).
Pattern Spotlight
DFS (Cycle Detection and Topological Sort)
Use DFS with a recursion stack to detect cycles in a directed graph; append nodes to the output list only after all their dependencies are resolved, ensuring a valid topological order if no cycles exist.
Solution
| 1 | class Solution:
|
| 2 | def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]:
|
| 3 | prereq = {c: [] for c in range(numCourses)}
|
| 4 | for crs, pre in prerequisites:
|
| 5 | prereq[crs].append(pre)
|
| 6 |
|
| 7 | output = []
|
| 8 | visit, cycle = set(), set()
|
| 9 | def dfs(crs):
|
| 10 | if crs in cycle:
|
| 11 | return False
|
| 12 | if crs in visit:
|
| 13 | return True
|
| 14 |
|
| 15 | cycle.add(crs)
|
| 16 | for pre in prereq[crs]:
|
| 17 | if not dfs(pre):
|
| 18 | return False
|
| 19 | cycle.remove(crs)
|
| 20 | visit.add(crs)
|
| 21 | output.append(crs)
|
| 22 | return True
|
| 23 |
|
| 24 | for c in range(numCourses):
|
| 25 | if not dfs(c):
|
| 26 | return []
|
| 27 | return output |
Step-by-Step Solution
Build Adjacency List Mapping Courses to Prerequisites
| 3 | prereq = {c: [] for c in range(numCourses)}
|
| 4 | for crs, pre in prerequisites:
|
| 5 | prereq[crs].append(pre)
|
Objective
To represent the course prerequisites as a graph for efficient traversal.
Key Insight
Representing the problem as a graph is essential. Using a dictionary mapping each course to a list of its prerequisites allows quick access to dependencies during DFS. This structure supports efficient cycle detection and ordering.
Interview Quick-Check
Core Logic
The adjacency list stores for each course the list of courses that must be taken before it, enabling direct traversal of dependencies.
Common Pitfalls & Bugs
Confusing the direction of edges (course to prerequisite vs prerequisite to course) can lead to incorrect traversal and cycle detection.
Perform DFS with Cycle Detection to Validate and Order Courses
To recursively explore each course's prerequisites, detect cycles, and build a valid course order.
Iterate Over All Courses to Trigger DFS and Return Final Ordering
To ensure all courses are processed and to return the computed valid order or an empty list if impossible.
2 more steps with full analysis available on Pro.
Line Analysis
This solution has 7 Critical lines interviewers watch for.
if crs in cycle:
Check if the current course is already in the recursion stack, indicating a cycle.
Detecting a node in the recursion stack signals a back edge and thus a cycle, which invalidates any course ordering.
output.append(crs)
Append the current course to the output list after all prerequisites are processed.
Postorder appending guarantees that prerequisites appear before dependent courses in the final ordering.
return False
Return False immediately upon cycle detection.
Aborting DFS on cycle detection prevents further exploration and signals that no valid ordering exists.
Full line-by-line criticality + rationale for all 21 lines available on Pro.
Test Your Understanding
Why is it necessary to track nodes currently in the recursion stack (the 'cycle' set) separately from the visited nodes?
See the answer with Pro.
Related Problems
DFS pattern
Don't just read it. Drill it.
Reconstruct Course Schedule II from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Course Schedule II drill