LeetCode 207: Course Schedule
Imagine you're registering for college courses β but some courses require you to finish others first. Can you actually graduate, or are you stuck in an infinite prerequisite loop? That's exactly what this problem asks. π
π§© The Problem
There are a total of
numCoursescourses you have to take, labeled from0tonumCourses - 1. You are given an arrayprerequisiteswhereprerequisites[i] = [aα΅’, bα΅’]indicates that you must take coursebα΅’first if you want to take courseaα΅’.Return
trueif you can finish all courses. Otherwise, returnfalse.
Examples
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Course 0 βββ Course 1
To take course 1, finish course 0 first.
So it is possible. β
Example 2:
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Course 0 βββ Course 1
β β
ββββββββββββββ
To take course 1, you need course 0.
To take course 0, you need course 1.
Impossible loop! β
Constraints
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= aα΅’, bα΅’ < numCourses- All the pairs
prerequisites[i]are unique.
π§ How to Think About This
This problem is really about detecting a cycle in a directed graph.
Think of it this way:
- Each course = a node
[a, b]means: to takea, you needbfirst β sob βββ a- If there is a cycle, you can never finish
Even simpler mental model
Imagine courses as tasks:
Wash dishes β Cook food β Eat
Valid. β
But:
Cook β Eat β Cook
Impossible loop. β
That's exactly what this problem asks.
Step 1 β Convert into a Graph
numCourses = 4
prerequisites = [[1,0],[2,1],[3,2]]
Meaning:
0 β 1 β 2 β 3
Possible. β
We build an adjacency list:
graph = {
0: [1],
1: [2],
2: [3],
3: []
}Step 2 β What Are We Actually Checking?
We need to know:
"Can I start from any course and eventually come back to it?"
That is cycle detection in a directed graph.
Step 3 β DFS Cycle Detection
While DFS runs, each node can be in one of 3 states:
| State | Meaning |
|---|---|
| 0 β Unvisited | We haven't seen this node yet |
| 1 β Visiting | Currently in the recursion stack (being explored right now) |
| 2 β Visited | Already fully processed β safe β |
π‘ Key Insight
If during DFS we reach a node already in "visiting" state:
A β B β C β A β visiting again!
We found a cycle. Return False.
Visual Walkthrough
prerequisites = [[1,0],[0,1]]
Graph:
0 β 1
1 β 0
DFS:
visit 0 β state[0] = 1 (visiting)
visit 1 β state[1] = 1 (visiting)
visit 0 β state[0] == 1 already!
β CYCLE DETECTED β
π» DFS Solution
class Solution(object):
def canFinish(self, numCourses, prerequisites):
# build graph
graph = {i: [] for i in range(numCourses)}
for course, prereq in prerequisites:
graph[prereq].append(course)
# 0 = unvisited
# 1 = visiting
# 2 = visited
state = [0] * numCourses
def dfs(course):
# cycle found
if state[course] == 1:
return False
# already checked
if state[course] == 2:
return True
# mark visiting
state[course] = 1
for neighbor in graph[course]:
if not dfs(neighbor):
return False
# mark visited
state[course] = 2
return True
# check every course
for course in range(numCourses):
if not dfs(course):
return False
return TrueComplexity
- Time: O(V + E)
- Space: O(V + E)
Where V = numCourses, E = number of prerequisites.
Step 4 β Can We Do Better?
The DFS solution is elegant, but there's an even cleaner and more interview-friendly approach: BFS Topological Sort (Kahn's Algorithm).
Instead of detecting cycles directly with DFS, we:
- Count how many prerequisites each course has (indegree)
- Start with courses having 0 prerequisites
- Remove them one by one
- Reduce dependency counts of neighbors
- If we can process all courses β possible β
- If some remain stuck β cycle exists β
Why this is nice
- No recursion β no stack overflow risk
- Easier to reason about in interviews
- Directly models the "take courses in order" intuition
π BFS Walkthrough
Example β No cycle:
numCourses = 4
prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Graph:
0 β 1
0 β 2
1 β 3
2 β 3
Indegree table:
Course 0 : 0 β no prerequisites
Course 1 : 1
Course 2 : 1
Course 3 : 2
Process:
Queue: [0] β start with indegree 0
Take 0:
β 1 indegree becomes 0 β add to queue
β 2 indegree becomes 0 β add to queue
completed = 1
Queue: [1, 2]
Take 1:
β 3 indegree becomes 1
completed = 2
Take 2:
β 3 indegree becomes 0 β add to queue
completed = 3
Queue: [3]
Take 3:
completed = 4
completed (4) == numCourses (4) β True β
Example β Cycle exists:
prerequisites = [[1,0],[0,1]]
Indegree:
Course 0 : 1
Course 1 : 1
No node has indegree 0!
Queue starts empty.
We cannot begin.
completed (0) != numCourses (2) β False β
π» Optimal Solution (BFS / Kahn's Algorithm)
from collections import deque
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
# adjacency list
graph = [[] for _ in range(numCourses)]
# indegree[i] = number of prerequisites for i
indegree = [0] * numCourses
# build graph
for course, prereq in prerequisites:
graph[prereq].append(course)
indegree[course] += 1
# courses with no prerequisites
queue = deque()
for i in range(numCourses):
if indegree[i] == 0:
queue.append(i)
completed = 0
while queue:
course = queue.popleft()
completed += 1
for neighbor in graph[course]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
return completed == numCoursesResults
Runtime: 4 ms β Beats 89.79%
Memory: 13.09 MB β Beats 93.22%
β±οΈ Complexity Analysis
| Approach | Time | Space |
|---|---|---|
| DFS Cycle Detection | O(V + E) | O(V + E) |
| BFS Topological Sort | O(V + E) | O(V + E) |
Both are optimal for graph traversal. The difference is in style, not performance.
π Comparison: DFS vs BFS
| Feature | DFS Cycle Detection | BFS Topological Sort |
|---|---|---|
| Approach | Recursion + 3-state marking | Queue + indegree counting |
| Cycle detection | Finds "visiting" node again | Can't process all nodes |
| Recursion risk | Deep recursion on large graphs | Iterative β no stack overflow |
| Code simplicity | Elegant and compact | Slightly more setup |
| Interview preference | Great for DFS questions | Often preferred β cleaner logic |
| Best for | Understanding recursion/cycles | Interviews + production |
β οΈ Common Mistakes
-
Building the graph in the wrong direction β
[a, b]meansbis a prerequisite fora, so the edge goesb β a. Reversing this breaks everything. -
Not checking all nodes β In DFS, you must start from every unvisited node (not just node 0). Disconnected components can hide cycles.
-
Using only 2 states in DFS β Without the 3-state system (unvisited / visiting / visited), you can't distinguish between a cycle and a cross-edge. This is the most common DFS bug.
-
Forgetting
dequein BFS β Using a list withpop(0)is O(n). Always usecollections.dequefor O(1) popleft.
π§ How to Recognize This Pattern
When you see these keywords in an interview:
- prerequisites
- dependencies
- ordering
- "can finish all"
- "possible to complete"
- scheduling
- build order
- package installation
Think:
GRAPH + TOPOLOGICAL SORT / CYCLE DETECTION
π― Tiny Trick to Remember Kahn's Algorithm
- Take all nodes with 0 dependencies
- Remove them
- Repeat
- If something can never become 0 β cycle exists
That's the entire algorithm in 4 lines. π
π― Interview Tips
-
Start with DFS β Show you understand cycle detection and recursion. Explain the 3-state system clearly.
-
Then offer BFS β "There's also a cleaner BFS approach using Kahn's algorithm..." Interviewers love seeing you know multiple approaches.
-
Draw the graph β Sketch the dependency graph on the whiteboard. It makes the cycle immediately visible.
-
Mention the pattern β "This is a classic topological sort problem" signals you recognize the category instantly.
-
Know the follow-ups β LeetCode 210 (Course Schedule II) asks you to return the actual ordering, not just true/false. The BFS approach naturally gives you the topological order.
