class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
order = []
g = defaultdict(list)
for a, b in prerequisites:
g[a].append(b)
UNVISITED, VISITING, VISITED = 0, 1, 2
states = [UNVISITED] * numCourses
def dfs(i):
if states[i] == VISITING:
return False
elif states[i] == VISITED:
return True
states[i] = VISITING
for nei in g[i]:
if not dfs(nei):
return False
states[i] = VISITED
order.append(i)
return True
for i in range(numCourses):
if not dfs(i):
return []
return order # Time: O(V + E), Space: O(V + E)
The Course Schedule II problem asks you to return a valid order in which to complete all courses given a list of prerequisites. If it’s impossible to complete all courses due to a cycle in the course dependencies, return an empty list.
This problem is a classic application of topological sorting in a directed graph. Each course is a node, and each prerequisite is a directed edge. To solve this, we use depth-first search (DFS) to build a valid course order and detect cycles.
Here is a step-by-step breakdown of how to solve the problem using DFS and a cycle detection strategy:
In a valid course order, all prerequisites must appear before the dependent course. Using post-order traversal during DFS naturally builds this order. Reversing the result ensures each course comes after its dependencies. If a cycle is found, we can’t finish all courses, and we return an empty list.
Time Complexity: O(V + E), where V is the number of courses and E is the number of prerequisites. Each node and edge is visited once.
Space Complexity: O(V + E), due to the adjacency list and recursive call stack.
The DFS-based topological sort is an efficient way to solve the Course Schedule II problem. It detects cycles in course prerequisites and builds a valid course completion order. This approach is scalable, fast, and leverages key graph theory principles used in many scheduling and dependency resolution problems.