2 of 3
There are numCourses courses labeled 0 to numCourses - 1. You're given a list of prerequisites where prerequisites[i] = [a, b] means you must take course b before course a.
Return True if you can finish all courses, or False if there's a cycle in the prerequisites (making it impossible).
This is LeetCode 207: Course Schedule.
print(can_finish(2, [[1, 0]])) # Output: True # Take course 0, then course 1 print(can_finish(2, [[1, 0], [0, 1]])) # Output: False # Course 0 requires 1, and course 1 requires 0 β cycle! print(can_finish(4, [[1, 0], [2, 1], [3, 2]])) # Output: True # Linear chain: 0 β 1 β 2 β 3 print(can_finish(4, [[1, 0], [2, 1], [3, 2], [0, 3]])) # Output: False # Cycle: 0 β 1 β 2 β 3 β 0
| Complexity | |
|---|---|
| Time | O(V + E) |
| Space | O(V + E) |
Where V = numCourses and E = number of prerequisites.
Why? We build an adjacency list (O(V + E)), then DFS visits each node and edge at most once.
Use the WHITE / GRAY / BLACK technique:
FalseTruedef can_finish(numCourses, prerequisites): # Your code here pass # Test your function: print(can_finish(2, [[1, 0]])) # Output: True print(can_finish(2, [[1, 0], [0, 1]])) # Output: False print(can_finish(4, [[1, 0], [2, 1], [3, 2]])) # Output: True print(can_finish(4, [[1, 0], [2, 1], [3, 2], [0, 3]])) # Output: False print(can_finish(5, [[1,0], [2,0], [3,1], [3,2], [4,3]])) # Output: True
For each prerequisite [a, b] (b must be taken before a), add an edge from b to a:
adj = {i: [] for i in range(numCourses)} for a, b in prerequisites: adj[b].append(a)
This means "after completing b, you can take a".
Use a dictionary or list to track each node's color:
WHITE, GRAY, BLACK = 0, 1, 2 color = [WHITE] * numCourses
color[node] = GRAYcolor[node] = BLACKA simple visited set isn't enough for directed graphs. Consider:
0 β 2
1 β 2
If we visit 2 from 0, then visit 2 again from 1, a simple "visited" check would wrongly think there's a cycle. The three-color approach distinguishes between "currently on the path" (GRAY) and "fully explored" (BLACK).
def can_finish(numCourses, prerequisites): adj = {i: [] for i in range(numCourses)} for a, b in prerequisites: adj[b].append(a) WHITE, GRAY, BLACK = 0, 1, 2 color = [WHITE] * numCourses def has_cycle(node): color[node] = GRAY for neighbor in adj[node]: if color[neighbor] == GRAY: return True if color[neighbor] == WHITE and has_cycle(neighbor): return True color[node] = BLACK return False for i in range(numCourses): if color[i] == WHITE: if has_cycle(i): return False return True
How it works:
has_cycle(node) returns True if a cycle is reachable from nodeWhat if the prerequisites could also have a self-loop like [1, 1] (course 1 requires itself)? Does your solution handle it? Test it!
print(can_finish(3, [[1, 0], [1, 1], [2, 1]])) # Output: False (course 1 requires itself β impossible!)
β Standard library: heapq, collections, itertools, math, random, functools, datetime, bisect
β Functions, classes, recursion, print()
β No file system, subprocess, OS access, or network requests
β No pip install (all supported modules are pre-loaded)
β±οΈ 5 second execution time limit