← Back to Graphs Part 2
πŸŽ“

Course Schedule β€” Can You Finish?

2 of 3

Exercise 2: Course Schedule β€” Can You Finish? πŸŽ“

Your Task

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.


Examples

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

Expected Complexity

Complexity
TimeO(V + E)
SpaceO(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.


Approach β€” DFS with 3 Colors

Use the WHITE / GRAY / BLACK technique:

  1. Build an adjacency list from the prerequisites
  2. Initialize every node as WHITE (unvisited)
  3. DFS from each WHITE node:
    • Mark it GRAY (in progress β€” on the current path)
    • Visit all neighbors recursively
    • If you hit a GRAY node β†’ cycle found! Return False
    • If BLACK β†’ already done, skip
    • Mark it BLACK when all neighbors are explored
  4. If no cycles found across all nodes, return True

Starter Code

def 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

Hints

πŸ’‘ Hint 1 β€” Build the adjacency list

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".

πŸ’‘ Hint 2 β€” Three-color DFS

Use a dictionary or list to track each node's color:

WHITE, GRAY, BLACK = 0, 1, 2 color = [WHITE] * numCourses
  • When you start visiting a node: color[node] = GRAY
  • When you finish visiting a node and all its neighbors: color[node] = BLACK
  • If you encounter a GRAY neighbor during DFS: that's a back edge β†’ cycle!
πŸ’‘ Hint 3 β€” Why not just track "visited"?

A 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).

βœ… Solution
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 node
  • We mark nodes GRAY when we enter them and BLACK when we leave
  • Hitting a GRAY node means we've found a back edge β€” the node is an ancestor in the current DFS path, so there's a cycle
  • BLACK nodes are safe to skip β€” they and all their descendants were fully explored with no cycle

Bonus Challenge 🌟

What 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!)
πŸ’»

Try it yourself

Code: Course Schedule β€” Can You Finish?

Loading Python runtime…
Python …
Loading...
Output
Click "Run" to execute your code...
ℹ️ About this Python environment

βœ… 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