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.
