Problem Statement in English
There are numCourses courses available. You’re given a list of preqrequisites, preqrequisites that some or all courses have. Your task is to return True, if all courses can be taken, and False if not.
Approach
The plan is to make an adjacency list representing any prequisites that a course has. In order to see if a particular course can be taken, we need to see if each of the courses it depends on can be taken, if they can, we can continue processing other prerequisites, otherwise we can straight away say that this course cannot be completed, as it depends on a course that cannot be completed.
Clearly, recursion is our friend here, as we can recursively process courses that this course depends on.
An optimisation we can make, is that as we recursively process a node, and we see that it can be completed, we can mark it as having no dependencies/prequisites, that way later on, while assessing another course, if this course shows up as a dependency, then we can straight away say that it can be completed, and move on.
Solution in Python
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# init dependency map with empty list
# (list comprehension)
dependencyMap = {i: [] for i in range(numCourses)}
# [a,b] where b is required to take a
for a, b in prerequisites:
# add every course that is required to take a
dependencyMap[a].append(b)
# seen set to keep track of visited nodes for each course we're analyzing
seen = set()
# recursive function to check if there are any cycles (dfs traversal)
def recursiveCall(key) -> bool:
# we're coming back to a node we've already visited, so there is a cycle
if key in seen:
return False
# if there are no dependencies for this course, we can take it
if dependencyMap[key] == []:
return True
# mark this node as visited
seen.add(key)
# iterate over all the dependencies of this course
for value in dependencyMap[key]:
# if there are no dependencies for this course, we can take it
if dependencyMap[value] == []:
# so go to next dependency
continue
# if there are dependencies, check if there is a cycle recursively
if not recursiveCall(value):
return False
# this course can be taken so we mark it as having no dependencies
dependencyMap[key] = []
# remove this course from the visited set
seen.discard(key)
# this course can be taken
return True
# iterate over courses to check if they can be taken
for k in dependencyMap:
# if there is a cycle, return False,
# since this course can't be taken
if not recursiveCall(k):
return False
# all courses can be taken
return True
Complexity
Time: $O(n)$
Since we process atmost $n$ courses.Space: $O(n)$
Since we can have atmost $n$ courses in the recursion stack.
Mistakes I Made
I made a whole bunch of mistakes in the implementation part, returning values from recursive calls and such. Nasty business to debug by the way. Whew.
And we are done.