You are given an array prerequisites where prerequisites[i] = [a, b] indicates that you must take course b first if you want to take course a.
[0, 1], indicates that to take course 0 you have to first take course 1.There are a total of numCourses courses you are required to take, labeled from 0 to numCourses - 1.
Return a valid ordering of courses you can take to finish all courses. If there are many valid answers, return any of them. If it's not possible to finish all courses, return an empty array.
Example 1:
Input: numCourses = 3, prerequisites = [[1,0]]
Output: [0,1,2]Explanation: We must ensure that course 0 is taken before course 1.
Example 2:
Input: numCourses = 3, prerequisites = [[0,1],[1,2],[2,0]]
Output: []Explanation: It's impossible to finish all courses.
Constraints:
1 <= numCourses <= 10000 <= prerequisites.length <= 1000prerequisite pairs are unique.
You should aim for a solution with O(V + E) time and O(V + E) space, where V is the number of courses (nodes) and E is the number of prerequisites (edges).
Consider the problem as a graph where courses represent the nodes, and prerequisite[i] = [a, b] represents a directed edge from a to b. We need to determine whether the graph contains a cycle. Why? Because if there is a cycle, it is impossible to complete the courses involved in the cycle. Can you think of an algorithm to detect a cycle in a graph and also find the valid ordering if a cycle doesn't exist?
We can use DFS to detect a cycle in a graph. However, we also need to find the valid ordering of the courses, which can also be achieved using DFS. Alternatively, we can use the Topological Sort algorithm to find the valid ordering in this directed graph, where the graph must be acyclic to complete all the courses, and the prerequisite of a course acts as the parent node of that course. How would you implement this?
We compute the indegrees of all the nodes. Then, we perform a BFS starting from the nodes that have no parents (indegree[node] == 0). At each level, we traverse these nodes, decrement the indegree of their child nodes, and append those child nodes to the queue if their indegree becomes 0. We only append nodes whose indegree is 0 or becomes 0 during the BFS to our result array. If the length of the result array is not equal to the number of courses, we return an empty array.
Before attempting this problem, you should be comfortable with:
Each course is a node, and each prerequisite is a directed edge.
We want an order of courses such that all prerequisites of a course are taken before it.
Using DFS, we:
cycle - tracks the current DFS path (for cycle detection)visit - tracks fully processed coursescycle, a cycle exists - return empty listclass Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
prereq = {c: [] for c in range(numCourses)}
for crs, pre in prerequisites:
prereq[crs].append(pre)
output = []
visit, cycle = set(), set()
def dfs(crs):
if crs in cycle:
return False
if crs in visit:
return True
cycle.add(crs)
for pre in prereq[crs]:
if dfs(pre) == False:
return False
cycle.remove(crs)
visit.add(crs)
output.append(crs)
return True
for c in range(numCourses):
if dfs(c) == False:
return []
return outputWhere is the number of courses and is the number of prerequisites.
Treat each course as a node and each prerequisite as a directed edge.
A course can be taken only when all its prerequisites are completed.
Kahn's Algorithm works by:
indegree = 0)If at the end some courses are still locked, it means a cycle exists, so no valid order is possible.
indegree for each courseindegree = number of prerequisites).indegree = 0 to a queue.indegree of its dependent courses.indegree = 0, add it to the queue.class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
indegree = [0] * numCourses
adj = [[] for i in range(numCourses)]
for src, dst in prerequisites:
indegree[dst] += 1
adj[src].append(dst)
q = deque()
for n in range(numCourses):
if indegree[n] == 0:
q.append(n)
finish, output = 0, []
while q:
node = q.popleft()
output.append(node)
finish += 1
for nei in adj[node]:
indegree[nei] -= 1
if indegree[nei] == 0:
q.append(nei)
if finish != numCourses:
return []
return output[::-1]Where is the number of courses and is the number of prerequisites.
We want an order of courses such that every course appears after its prerequisites.
This approach mixes Topological Sorting with DFS-style traversal.
The idea is:
indegree = 0)indegree of courses that depend on itindegree becomes 0, it is now safe to take, so we continue DFS from itIf we can visit all courses this way, a valid order exists.
If not, a cycle is present, making it impossible.
indegree for each course.indegree = 0, start a DFS.indegree of its neighbors.indegree becomes 0, recursively DFS on it.class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
adj = [[] for i in range(numCourses)]
indegree = [0] * numCourses
for nxt, pre in prerequisites:
indegree[nxt] += 1
adj[pre].append(nxt)
output = []
def dfs(node):
output.append(node)
indegree[node] -= 1
for nei in adj[node]:
indegree[nei] -= 1
if indegree[nei] == 0:
dfs(nei)
for i in range(numCourses):
if indegree[i] == 0:
dfs(i)
return output if len(output) == numCourses else []Where is the number of courses and is the number of prerequisites.
In DFS-based topological sort, you need two separate tracking mechanisms: one for nodes currently in the DFS path (for cycle detection) and one for fully processed nodes. Using only one set leads to either false cycle detection or infinite loops.
# Wrong: Using single visited set
def dfs(course):
if course in visited:
return False # Incorrectly treats revisit as cycle
visited.add(course)
for pre in prereq[course]:
if not dfs(pre):
return False
return True
# Correct: Separate cycle and visit tracking
def dfs(course):
if course in cycle: # Currently in path = cycle
return False
if course in visit: # Already processed = skip
return True
cycle.add(course)
for pre in prereq[course]:
if not dfs(pre):
return False
cycle.remove(course)
visit.add(course)
return TrueThe prerequisite pair [a, b] means "to take course a, you must first take course b". Building edges in the wrong direction results in an incorrect topological order.
# Wrong: Edge direction reversed
for crs, pre in prerequisites:
adj[pre].append(crs) # This builds "pre -> crs" but for Kahn's we may need opposite
# The correct direction depends on your algorithm:
# For DFS that adds after processing: course -> prerequisites
# For Kahn's: prerequisite -> course (then reverse) or course -> prerequisiteCourses with no prerequisites and no dependents still need to be included in the output. Forgetting to iterate over all courses means some valid courses might be missing from the result.
# Wrong: Only processing courses that appear in prerequisites
for crs, pre in prerequisites:
# Only touches courses mentioned in prerequisites
# Correct: Process all courses from 0 to numCourses-1
for i in range(numCourses):
if indegree[i] == 0:
queue.append(i)