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.
The pair [0, 1], indicates that must take course 1 before taking course 0.
There are a total of numCourses courses you are required to take, labeled from 0 to numCourses - 1.
Return true if it is possible to finish all courses, otherwise return false.
Example 1:
Input: numCourses = 2, prerequisites = [[0,1]]
Output: trueExplanation: First take course 1 (no prerequisites) and then take course 0.
Example 2:
Input: numCourses = 2, prerequisites = [[0,1],[1,0]]
Output: falseExplanation: In order to take course 1 you must take course 0, and to take course 0 you must take course 1. So it is impossible.
Constraints:
1 <= numCourses <= 10000 <= prerequisites.length <= 1000prerequisites[i].length == 20 <= a[i], b[i] < numCoursesprerequisite 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 cycle in a graph?
We can use the Depth First Search (DFS) algorithm to detect a cycle in a graph. We iterate over each course, run a DFS from that course, and first try to finish its prerequisite courses by recursively traversing through them. To detect a cycle, we initialize a hash set called path, which contains the nodes visited in the current DFS call. If we encounter a course that is already in the path, we can conclude that a cycle is detected. How would you implement it?
We run a DFS starting from each course by initializing a hash set, path, to track the nodes in the current DFS call. At each step of the DFS, we return false if the current node is already in the path, indicating a cycle. We recursively traverse the neighbors of the current node, and if any of the neighbor DFS calls detect a cycle, we immediately return false. Additionally, we clear the neighbors list of a node when no cycle is found from that node to avoid revisiting those paths again.
Before attempting this problem, you should be comfortable with:
Each course is a node, and each prerequisite is a directed edge.
You can finish all courses only if there is no cycle in this directed graph.
A cycle means:
We use DFS with cycle detection:
visiting set to track the current DFS path.visiting, return false (cycle).true.class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Map each course to its prerequisites
preMap = {i: [] for i in range(numCourses)}
for crs, pre in prerequisites:
preMap[crs].append(pre)
# Store all courses along the current DFS path
visiting = set()
def dfs(crs):
if crs in visiting:
# Cycle detected
return False
if preMap[crs] == []:
return True
visiting.add(crs)
for pre in preMap[crs]:
if not dfs(pre):
return False
visiting.remove(crs)
preMap[crs] = []
return True
for c in range(numCourses):
if not dfs(c):
return False
return TrueWhere is the number of courses and is the number of prerequisites.
Treat each course as a node and each prerequisite as a directed edge.
If a course has no prerequisites, it can be taken immediately.
Kahn's Algorithm repeatedly takes courses that have zero prerequisites.
When we finish a course, we remove its dependency effect from other courses.
truefalseindegree (number of prerequisites) for each course.indegree = 0 into a queue.indegree of its dependent courses.indegree = 0, add it to the queue.truefalseclass Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
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 = 0
while q:
node = q.popleft()
finish += 1
for nei in adj[node]:
indegree[nei] -= 1
if indegree[nei] == 0:
q.append(nei)
return finish == numCoursesWhere is the number of courses and is the number of prerequisites.
A single visited set marks nodes as seen globally, but cycle detection requires knowing if a node is in the current DFS path. Without tracking the recursion path separately, you may miss cycles or falsely detect them.
# Wrong: single visited set
visited = set()
def dfs(node):
if node in visited:
return False # This doesn't distinguish "finished" from "in current path"
visited.add(node)
for nei in adj[node]:
if not dfs(nei):
return False
return True
# Correct: track visiting (current path) and visited (finished)
visiting = set()
def dfs(node):
if node in visiting:
return False # Cycle: node is in current recursion path
if preMap[node] == []:
return True # Already processed, no cycle
visiting.add(node)
for nei in preMap[node]:
if not dfs(nei):
return False
visiting.remove(node)
preMap[node] = [] # Mark as processed
return TrueIf you only start DFS from one node, you may miss cycles in disconnected parts of the graph. Always iterate through all courses and run DFS from each unvisited node.
# Wrong: only check from course 0
return dfs(0)
# Correct: check all courses
for c in range(numCourses):
if not dfs(c):
return False
return TrueThe prerequisite [a, b] means "to take course a, you must first take course b". Mixing up which course maps to which leads to checking the wrong dependencies and missing or falsely reporting cycles.