Loading problems...
TL;DR: The optimal solution models the courses and prerequisites as a directed graph and performs a topological sort using Depth-First Search (DFS) with three-state coloring to detect cycles and determine the execution order.
In the LeetCode 210 problem, "Course Schedule II," you are given a specific number of courses and a list of prerequisites. Each prerequisite is defined as a pair [a, b], meaning you must take course b before you can take course a. Your objective is to return a valid linear ordering of courses that respects all prerequisite constraints. If a circular dependency exists (e.g., A requires B, and B requires A), it is impossible to complete the courses, and you must return an empty array.
This is a classic graph theory problem that tests your ability to model dependencies and validate the structure of a Directed Acyclic Graph (DAG).
A naive brute force approach would attempt to generate every possible permutation of the courses and verify if any permutation satisfies the prerequisite constraints.
0 to numCourses - 1.prerequisites list.[a, b] is satisfied (i.e., b appears before a in the permutation).Pseudo-code:
textfunction solve_brute_force(n, prerequisites): permutations = generate_all_permutations(0 to n-1) for perm in permutations: valid = true for [a, b] in prerequisites: if index_of(b, perm) > index_of(a, perm): valid = false break if valid: return perm return []
Why this fails: The time complexity of generating all permutations is . For (the constraint in this problem), is an astronomically large number, far exceeding the computational limits of any machine. This approach will result in a Time Limit Exceeded (TLE) error immediately.
The core insight for solving LeetCode 210 lies in understanding that the problem asks for a Topological Sort of a directed graph. A valid topological ordering is a linear ordering of vertices such that for every directed edge , vertex comes before in the ordering.
The critical constraint is that a topological sort is impossible if the graph contains a cycle. Therefore, our algorithm must serve two purposes simultaneously:
To achieve this efficiently, we use Depth-First Search (DFS) with Three-Color States. Instead of a simple boolean visited array, we track the state of each node during the traversal:
Visual Description: Imagine the DFS traversal as a path being drawn through the graph. When we move from node A to node B, we mark A as "Visiting". If the path eventually leads back to A while A is still marked "Visiting", we have closed a loop, indicating a cycle. If we finish processing all neighbors of B without finding a cycle, we mark B as "Visited" and verify that it is safe to add to our schedule. The order in which nodes are marked "Visited" (State 2) gives us the reverse topological order.

prerequisites list into an adjacency list. Note that an input [a, b] means an edge exists from b to a (b must be taken before a).state of size numCourses with all values set to 0 (Unvisited).0 to numCourses - 1. If a course is in State 0, initiate a DFS traversal from that node. This handles disconnected graph components.1 (Visiting), a cycle is detected. Return false.2 (Visited), it is already processed. Return true.1.false, propagate the failure up the stack.2 (Visited).Let's trace the algorithm with numCourses = 4 and prerequisites = [[1,0], [2,0], [3,1], [3,2]].
Edges: , , , .
state = [0, 0, 0, 0], result = [].0 (State 0). Call dfs(0).0 as State 1.0 are 1 and 2.1 as State 1.1 is 3.3 as State 1.3 as State 2.3 to result. result = [3].true.1 as State 2.1 to result. result = [3, 1].true.2 as State 1.2 is 3.3 is State 2 (Visited). Return true.2 as State 2.2 to result. result = [3, 1, 2].true.0 as State 2.0 to result. result = [3, 1, 2, 0].true.result to get [0, 2, 1, 3]. This is a valid topological order.The correctness of this algorithm relies on the properties of Depth-First Search on a DAG.
cpp#include <vector> #include <algorithm> using namespace std; class Solution { public: // 0 = Unvisited, 1 = Visiting, 2 = Visited bool dfs(int node, vector<vector<int>>& adj, vector<int>& state, vector<int>& result) { if (state[node] == 1) return false; // Cycle detected if (state[node] == 2) return true; // Already processed state[node] = 1; // Mark as visiting for (int neighbor : adj[node]) { if (!dfs(neighbor, adj, state, result)) { return false; } } state[node] = 2; // Mark as visited result.push_back(node); // Add to result (post-order) return true; } vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) { vector<vector<int>> adj(numCourses); // Build adjacency list: [a, b] means b -> a for (const auto& edge : prerequisites) { adj[edge[1]].push_back(edge[0]); } vector<int> state(numCourses, 0); vector<int> result; for (int i = 0; i < numCourses; ++i) { if (state[i] == 0) { if (!dfs(i, adj, state, result)) { return {}; // Cycle detected, return empty } } } reverse(result.begin(), result.end()); return result; } };
javaimport java.util.ArrayList; import java.util.Collections; import java.util.List; class Solution { // 0 = Unvisited, 1 = Visiting, 2 = Visited private boolean dfs(int node, List<List<Integer>> adj, int[] state, List<Integer> result) { if (state[node] == 1) return false; // Cycle detected if (state[node] == 2) return true; // Already processed state[node] = 1; // Mark as visiting for (int neighbor : adj.get(node)) { if (!dfs(neighbor, adj, state, result)) { return false; } } state[node] = 2; // Mark as visited result.add(node); // Add to result (post-order) return true; } public int[] findOrder(int numCourses, int[][] prerequisites) { List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < numCourses; i++) { adj.add(new ArrayList<>()); } // Build adjacency list: [a, b] means b -> a for (int[] edge : prerequisites) { adj.get(edge[1]).add(edge[0]); } int[] state = new int[numCourses]; List<Integer> resultList = new ArrayList<>(); for (int i = 0; i < numCourses; i++) { if (state[i] == 0) { if (!dfs(i, adj, state, resultList)) { return new int[0]; // Cycle detected } } } // Reverse to get topological order Collections.reverse(resultList); // Convert List to int[] int[] result = new int[numCourses]; for (int i = 0; i < numCourses; i++) { result[i] = resultList.get(i); } return result; } }
pythonfrom collections import defaultdict class Solution: def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]: adj = defaultdict(list) # Build adjacency list: [a, b] means b -> a for dest, src in prerequisites: adj[src].append(dest) # 0 = Unvisited, 1 = Visiting, 2 = Visited state = [0] * numCourses result = [] def dfs(node): if state[node] == 1: return False # Cycle detected if state[node] == 2: return True # Already processed state[node] = 1 # Mark as visiting for neighbor in adj[node]: if not dfs(neighbor): return False state[node] = 2 # Mark as visited result.append(node) # Add to result (post-order) return True for i in range(numCourses): if state[i] == 0: if not dfs(i): return [] return result[::-1] # Reverse to get topological order
Time Complexity:
numCourses and is the size of prerequisites.Space Complexity:
The Graph DFS - Cycle Detection pattern used in LeetCode 210 is directly applicable to several other popular interview questions:
Why does my solution get Time Limit Exceeded (TLE) even with DFS?
visited boolean set that resets after every DFS call, or failing to memoize "Visited" (State 2) nodes globally.Why is my output order incorrect?
Why does my code fail on disconnected graphs?
0 or only on nodes that appear in .prerequisites0, resulting in an incomplete course list. You must iterate 0 to numCourses - 1.Why can't I just use a simple visited set for cycle detection?