Loading problems...
TL;DR: To solve LeetCode 207, model the courses as a directed graph and use Depth-First Search (DFS) to detect if a cycle exists; if a cycle is found, it is impossible to finish all courses.
The Course Schedule problem asks whether it is possible to complete a specific number of courses given a list of prerequisites. Each prerequisite is a dependency: to take course A, you must first complete course B. This relationship forms a directed edge from B to A. If a set of courses forms a circular dependency (e.g., A depends on B, B depends on A), it is impossible to complete them. This is a classic "deadlock" detection problem.
A naive brute force approach attempts to simulate taking courses by checking every possible path for loops. For each course, we could initiate a traversal to see if we eventually return to the starting course.
Pseudo-code:
textFor each course i from 0 to numCourses - 1: Start a DFS from i If the DFS path encounters i again: Return False (Cycle detected) Return True
Why it fails: The time complexity of this approach is inefficient. Without memorizing which nodes have already been fully processed and verified to be cycle-free, the algorithm effectively re-traverses large portions of the graph repeatedly.
The core insight that moves us from brute force to the optimal solution is the use of state tracking during DFS. Instead of just marking a node as "visited," we need to distinguish between three states to handle directed cycles correctly:
The Invariant: A cycle exists in a directed graph if and only if, during a DFS traversal, we encounter a node that is currently in the Visiting state. This is known as a "back edge."
If we encounter a node marked Visited, we stop exploring that path immediately because we know that the subgraph reachable from that node is already verified to be safe (cycle-free), avoiding redundant work.
Visual Description: Imagine the recursion tree. When the algorithm moves from Course A to Course B, both are marked "Visiting." If Course B has a dependency on Course A, the algorithm sees that A is already "Visiting" (in the current stack). This confirms a circular dependency. If the algorithm finishes exploring B and backtracks to A without issues, B is marked "Visited" (safe/done).

Let's trace the execution with numCourses = 2 and prerequisites = [[1,0], [0,1]] (Cycle 0 <-> 1).
0 -> [1], 1 -> [0].0. State is Unvisited. Call dfs(0).0 as Visiting.0: Neighbor is 1.1 is Unvisited. Call dfs(1).1 as Visiting.1: Neighbor is 0.0 is currently Visiting.1 -> 0 while 0 is in the recursion stack.true.false for the problem.The algorithm relies on the properties of Depth-First Search on directed graphs. The "Three-Color" (or three-state) method is mathematically proven to detect cycles.
Visited (2) without ever encountering a Visiting (1) node.Visiting. Therefore, the condition state[neighbor] == Visiting is necessary and sufficient to detect a cycle.cppclass Solution { public: bool canFinish(int numCourses, vector<vector<int>>& prerequisites) { // Build Adjacency List vector<vector<int>> adj(numCourses); for (const auto& edge : prerequisites) { adj[edge[1]].push_back(edge[0]); } // States: 0 = Unvisited, 1 = Visiting, 2 = Visited vector<int> state(numCourses, 0); for (int i = 0; i < numCourses; ++i) { if (state[i] == 0) { if (hasCycle(i, adj, state)) { return false; } } } return true; } private: bool hasCycle(int node, const vector<vector<int>>& adj, vector<int>& state) { state[node] = 1; // Mark as Visiting (in recursion stack) for (int neighbor : adj[node]) { if (state[neighbor] == 1) { return true; // Cycle detected: back edge to a node in stack } if (state[neighbor] == 0) { if (hasCycle(neighbor, adj, state)) { return true; } } } state[node] = 2; // Mark as Visited (fully processed) return false; } };
javaclass Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { // Build Adjacency List List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < numCourses; i++) { adj.add(new ArrayList<>()); } for (int[] edge : prerequisites) { adj.get(edge[1]).add(edge[0]); } // States: 0 = Unvisited, 1 = Visiting, 2 = Visited int[] state = new int[numCourses]; for (int i = 0; i < numCourses; i++) { if (state[i] == 0) { if (hasCycle(i, adj, state)) { return false; } } } return true; } private boolean hasCycle(int node, List<List<Integer>> adj, int[] state) { state[node] = 1; // Mark as Visiting for (int neighbor : adj.get(node)) { if (state[neighbor] == 1) { return true; // Cycle detected } if (state[neighbor] == 0) { if (hasCycle(neighbor, adj, state)) { return true; } } } state[node] = 2; // Mark as Visited return false; } }
pythonclass Solution: def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: # Build Adjacency List adj = [[] for _ in range(numCourses)] for dest, src in prerequisites: adj[src].append(dest) # States: 0 = Unvisited, 1 = Visiting, 2 = Visited state = [0] * numCourses def has_cycle(node): state[node] = 1 # Mark as Visiting for neighbor in adj[node]: if state[neighbor] == 1: return True # Cycle detected if state[neighbor] == 0: if has_cycle(neighbor): return True state[node] = 2 # Mark as Visited return False for i in range(numCourses): if state[i] == 0: if has_cycle(i): return False return True
Time Complexity:
numCourses and is the length of prerequisites.Space Complexity:
The "Graph DFS - Cycle Detection" pattern is highly reusable. Understanding the 3-state coloring logic is crucial for solving these related problems:
Why do I get Time Limit Exceeded (TLE) with a simple boolean visited array?
Why is my solution failing on disconnected graphs?
0.0 might not have a path to the component containing the cycle.true (can finish) even when a cycle exists in a disconnected component. You must loop through 0 to .numCourses - 1Why can't I just check if node == neighbor?