Course Schedule
Clarify the problem:
- The problem requires us to determine if it is possible to finish all courses given a list of prerequisites.
- We need to check if there is a valid course schedule that satisfies all the prerequisites.
- A course schedule is valid if we can complete all the courses in the given list of prerequisites without violating any dependencies.
- It is assumed that there are no cyclic dependencies in the course prerequisites.
Analyze the problem:
- Input: An integer
numCourses
representing the total number of courses, and a list of tuplesprerequisites
representing the prerequisites for each course. - Output: A boolean indicating whether it is possible to finish all courses.
- Constraints:
- 2 <= numCourses <= 10^5
- 0 <= prerequisites.length <= 5000
- prerequisites[i].length == 2
- 0 <= prerequisites[i][0], prerequisites[i][1] < numCourses
- The input prerequisites list does not contain any duplicate pairs.
- Input: An integer
Design an algorithm:
- We can solve this problem using a graph-based approach.
- First, we need to create a directed graph representation of the courses and their prerequisites.
- We can use a dictionary to represent the graph, where the keys are the course numbers, and the values are lists of course numbers representing the prerequisites.
- After constructing the graph, we can perform a depth-first search (DFS) traversal on the graph to check for any cycles.
- If we encounter a cycle during the DFS traversal, it means there is a circular dependency among the courses, and it is not possible to finish all courses.
- If no cycle is found, it means it is possible to finish all courses.
Explain your approach:
- We will implement a class called
CourseSchedule
to encapsulate the solution. - The class will have the following methods:
buildGraph(numCourses, prerequisites)
: Constructs the directed graph representation of the courses and prerequisites.hasCycle(course)
: Performs a depth-first search (DFS) traversal on the graph to check for cycles, starting from the given course.canFinish(numCourses, prerequisites)
: Checks if it is possible to finish all courses given the total number of courses and the prerequisites.
- We will implement a class called
Write clean and readable code:
pythonclass CourseSchedule: def __init__(self): self.graph = {} def buildGraph(self, numCourses, prerequisites): self.graph = {course: [] for course in range(numCourses)} for course, prereq in prerequisites: self.graph[course].append(prereq) def hasCycle(self, course): if course in self.visiting: return True if course in self.visited: return False self.visiting.add(course) for prereq in self.graph[course]: if self.hasCycle(prereq): return True self.visiting.remove(course) self.visited.add(course) return False def canFinish(self, numCourses, prerequisites): self.buildGraph(numCourses, prerequisites) self.visiting = set() self.visited = set() for course in range(numCourses): if self.hasCycle(course): return False return True
Test your code:
pythonschedule = CourseSchedule() print(schedule.canFinish(2, [[1, 0]])) # Output: True print(schedule.canFinish(2, [[1, 0], [0, 1]])) # Output: False print(schedule.canFinish(3, [[0, 1], [0, 2]])) # Output: True print(schedule.canFinish(3, [[0, 1], [1, 2], [2, 0]])) # Output: False
Explanation:
- We test the solution with different scenarios, including cases with valid course schedules and cases with cyclic dependencies.
- In the first test case, there is only one prerequisite relationship (1 -> 0), and it is possible to finish all courses.
- In the second test case, there is a cyclic dependency between courses 0 and 1, making it impossible to finish all courses.
- In the third test case, there is a valid course schedule without any cyclic dependencies.
- In the fourth test case, there is a cyclic dependency among courses 0, 1, and 2, making it impossible to finish all courses.
Optimize if necessary:
- The solution utilizes a graph-based approach with a depth-first search (DFS) traversal to detect cycles.
- It is a commonly used and efficient approach to solve this type of problem, and further optimization is not necessary.
Handle error cases:
- The code assumes that the input follows the problem constraints. It does not explicitly handle invalid input cases.
Discuss complexity analysis:
- Let N be the total number of courses, and E be the total number of prerequisites.
- Building the graph takes O(E) time since we iterate through all the prerequisites.
- The DFS traversal to check for cycles has a time complexity of O(N + E) in the worst case, as we visit each course and each prerequisite once.
- Therefore, the overall time complexity of the
canFinish
function is O(N + E). - The space complexity is O(N + E) as well since we use additional sets and dictionaries to store the graph and visited nodes.