207. Course Schedule
Medium
Description
There are a total of numCourses
courses you have to take, labeled from 0
to numCourses - 1
. 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α΅’
.
- For example, the pair
[0, 1]
, indicates that to take course0
you have to first take course1
.
Return true
if you can finish all courses. Otherwise, return false
.
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take.
To take course 1 you should have finished course 0. So it is possible.
Example 2:
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take.
To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
Constraints:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
- All the pairs prerequisites[i] are unique.
Solutions π
Approach:Topological Sorting using BFS
Time complexity: \(O(β£Vβ£+β£Eβ£)\),
Space complexity: \(O(β£Vβ£+β£Eβ£)\), where \(β£Vβ£=numCourses\) and \(β£Eβ£=β£prerequisitesβ£\)
Algorithm Discussion
Wny Topological Sort Algorithm on Directed Graphs? β via BFS
-
The Topological sorting gives an order that we can visit the vertices in a directed graphs where the prerequisite vertices need to be visited first.
-
A prerequisite or dependency is a edge with direction. We can first start with all nodes that have zero indegree (meaning no edges coming to them) β and then we can decrement the indegree of all out nodes as we deque a current vertex.
-
Once a out node has become zero indegree, we can visit it β thus pushing to the queue.
Algorithm
Topological Sorting
For this problem, we can consider the courses
as \(nodes\) in a graph, and prerequisites
as \(edges\) in the graph.
Thus, we can transform this problem into determining whether there is a cycle in the directed graph.
Specifically, we can use the idea of topological sorting. For each node with an in-degree of 0, we reduce the in-degree of its out-degree nodes by 1, until all nodes have been traversed.
If all nodes have been traversed, it means there is no cycle in the graph, and we can complete all courses; otherwise, we cannot complete all courses.
The time complexity is O(n+m), and the space complexity is O(n+m). Here, n and m are the number of courses and prerequisites respectively.
class Node(object):
def __init__(self):
self.inDegrees = 0
self.outNodes = []
class Solution(object):
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Build the graph.
G = defaultdict(Node)
for a, b in prerequisites: # a-start , b-end of edges aka prerequisites
G[a].inDegrees += 1
G[b].outNodes.append(a)
# Initialize queue with courses that have zero in-degrees
q = deque()
for i in range(numCourses):
if G[i].inDegrees == 0:
q.append(i)
#Perform topological sorting.
#Apply BFS on Graph
cnt = 0
while q:
v = q.popleft() #v - current vertex
cnt+=1
for a in G[v].outNodes:
G[a].inDegrees -= 1
if G[a].inDegrees == 0:
q.append(a)
return cnt==numCourses
from collections import defaultdict
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
g = defaultdict(list)
indeg = [0] * numCourses
for a, b in prerequisites:
g[b].append(a)
indeg[a] += 1
cnt = 0
q = deque(i for i, x in enumerate(indeg) if x == 0)
while q:
i = q.popleft() # i - current node
cnt += 1
for j in g[i]: # j - neighbours
indeg[j] -= 1
if indeg[j] == 0:
q.append(j)
return cnt == numCourses
Note
When the queue is exited (the BFS terminates), if the number of indegrees we decrement is not equal to the number of edges, then there is a Cycle in the Graph β hence we cannot visit all the nodes.
Approach DFS
Time complexity: \(O(β£Vβ£+β£Eβ£)\),
Space complexity: \(O(β£Vβ£+β£Eβ£)\), where \(β£Vβ£=numCourses\) and \(β£Eβ£=β£prerequisitesβ£\)
Algorithm2
To solve this problem, we can also use a depth-first search (DFS) approach to detect cycles in the course dependency graph.
If there's a cycle, it means we can't finish all courses.
We'll represent the graph using an adjacency list. Then, we'll perform DFS for each course.
During DFS, we'll keep track of visited courses and courses in the current path. If we encounter a course that's already in the current path, we've found a cycle.
The time complexity of this solution is \(O(V + E)\), where \(V\) is the number of courses and \(E\) is the number of prerequisites. Thatβs because we visit each course once and each prerequisite once.
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
#Create adjacency List
G = [[] for _ in range(numCourses)]
for course , prereq in prerequisites:
G[course].append(prereq)
# Track visited course and current path
vis = set()
path = set()
def dfs(course) -> bool:
# If course is in path, we found a cycle
if course in path:
return False
# If course is already visited, its safe
if course in vis:
return True
# Add course to current path
path.add(course)
# Check all prerequisite
for prereq in G[course]:
if not dfs(prereq):
return False
# Remove course from current path
path.remove(course)
# Mark course as visited
vis.add(course)
return True
#Check all courses
for course in range(numCourses):
if not dfs(course):
return False
return True
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
#Create adjacency List
G = [[] for _ in range(numCourses)]
for course , prereq in prerequisites:
G[course].append(prereq)
# Track visited course and current path
vis = set()
path = set()
#Check all courses
for course in range(numCourses):
if not self.dfs(course, vis, path, G):
return False
return True
def dfs(self, course: List[int], vis: {}, path: {}, G:List[List[int]]) -> bool:
# If course is in path, we found a cycle
if course in path:
return False
# If course is already visited, its safe
if course in vis:
return True
# Add course to current path
path.add(course)
# Check all prerequisite
for prereq in G[course]:
if not self.dfs(prereq, vis, path, G):
return False
# Remove course from current path
path.remove(course)
# Mark course as visited
vis.add(course)
return True