You are given numCourses
courses labeled from 0
to numCourses - 1
. You are also given a list of prerequisites, where prerequisites[i] = [a_i, b_i]
means you must take course b_i
before course a_i
. For example, [0, 1]
means you must take course 1
before course 0
.
Your task is to return an ordering of courses that allows you to complete all courses. If multiple orderings are possible, return any one of them. If it's impossible to complete all courses due to a circular dependency, return an empty array.
Example 1:
numCourses = 2, prerequisites = [[1,0]]
Output: [0,1]
(To take course 1, you must take course 0 first)
Example 2:
numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: [0,2,1,3]
(Course 3 depends on 1 and 2, which depend on 0)
Example 3:
numCourses = 1, prerequisites = []
Output: [0]
(A single course with no dependencies)
Develop an efficient algorithm to solve this course scheduling problem. Consider edge cases such as circular dependencies and courses with no dependencies.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you have a list of courses where some courses need to be taken before others. The brute force method is like trying every possible order of courses to see if it works, ensuring you always take prerequisite courses first. If we find a valid order after trying all the possibilities, that's our schedule.
Here's how the algorithm would work step-by-step:
def find_course_schedule(number_of_courses, prerequisites):
import itertools
all_courses = list(range(number_of_courses))
# Generate all possible course orders
for possible_order in itertools.permutations(all_courses):
is_valid = True
# Check if the current order satisfies prerequisites
for course, prerequisite in prerequisites:
try:
course_index = possible_order.index(course)
prerequisite_index = possible_order.index(prerequisite)
except ValueError:
is_valid = False
break
#A course cannot be taken before its prerequisite.
if course_index < prerequisite_index:
is_valid = False
break
#If the order is valid, return the schedule.
if is_valid:
return list(possible_order)
#If no valid order is found, return an empty list.
return []
The goal is to find a valid order to take courses, respecting prerequisites. We solve this by thinking about course dependencies and only listing courses that have no remaining prerequisites. The method focuses on systematically removing courses from the dependency graph as we add them to our order.
Here's how the algorithm would work step-by-step:
def find_order(number_of_courses, prerequisites):
course_prerequisites_count = [0] * number_of_courses
adjacency_list = [[] for _ in range(number_of_courses)]
for course, prerequisite in prerequisites:
course_prerequisites_count[course] += 1
adjacency_list[prerequisite].append(course)
# Initialize list with courses that have no prerequisites.
no_prerequisites_courses = [
course for course in range(number_of_courses)
if course_prerequisites_count[course] == 0
]
course_schedule = []
courses_completed = 0
while no_prerequisites_courses:
# Get a course with no remaining prerequisites.
course = no_prerequisites_courses.pop(0)
course_schedule.append(course)
courses_completed += 1
# Update the prerequisite count of dependent courses.
for dependent_course in adjacency_list[course]:
course_prerequisites_count[dependent_course] -= 1
# Add to the no_prerequisites list if count is 0.
if course_prerequisites_count[dependent_course] == 0:
no_prerequisites_courses.append(dependent_course)
# If all courses were completed, return the schedule.
if courses_completed == number_of_courses:
return course_schedule
# Otherwise, there is a cycle, return an empty list.
else:
return []
Case | How to Handle |
---|---|
numCourses is 0 or negative | Return an empty array as no courses can be taken. |
prerequisites is null or empty | Return a list containing all courses from 0 to numCourses-1 since there are no dependencies. |
prerequisites contains a cycle (impossible to finish all courses) | Topological sort detects cycles and returns an empty array if one is found. |
numCourses is very large (e.g., 10000) and prerequisites is relatively small | Adjacency list representation of the graph avoids unnecessary iterations and makes the solution scalable. |
prerequisites contains duplicate entries (e.g., [1, 0] and [1, 0]) | The adjacency list and indegree calculation will simply be updated redundantly, but the correctness will not be affected. |
A course has no prerequisites | These courses will have an indegree of 0 and will be added to the queue for processing. |
All courses depend on each other in a single chain | Topological sort correctly handles this linear dependency and produces the correct ordering. |
Integer overflow when calculating indegree for a very large number of prerequisites pointing to a single course | The indegree count uses integers, but the problem constraints limit the number of courses, preventing integer overflow. |