
Problem Statement
The task is to determine a valid sequence of courses based on given prerequisites. Each course is identified by a unique integer ranging from 0
to numCourses - 1
. The prerequisites
array contains pairs such that the second element in each pair must be completed before the first element can be taken. These prerequisites directly influence the valid order in which courses can be undertaken, constructing a dependency sequence. The problem requires finding an order of course completion that satisfies all the given dependencies. If a valid order exists, return any one of them; if it's not possible to complete all courses (due to circular dependencies or other issues), then an empty array should be returned.
Examples
Example 1
Input:
numCourses = 2, prerequisites = [[1,0]]
Output:
[0,1]
Explanation:
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1].
Example 2
Input:
numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output:
[0,2,1,3]
Explanation:
There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3].
Example 3
Input:
numCourses = 1, prerequisites = []
Output:
[0]
Constraints
1 <= numCourses <= 2000
0 <= prerequisites.length <= numCourses * (numCourses - 1)
prerequisites[i].length == 2
0 <= ai, bi < numCourses
ai != bi
- All the pairs
[ai, bi]
are distinct.
Approach and Intuition
To solve this problem, a typical approach involves using graph theory, specifically a technique known as topological sorting. Each course can be represented as a node in a graph, and each prerequisite pair as a directed edge from one node to another. The goal is to produce any topological ordering of the courses that respects the direction of edges.
- Representation: Convert the list of prerequisites into an adjacency list, representing the directed edges from pre-required courses to consequent courses.
- Initialization: Start with courses that have no prerequisites. These courses can be initially added to the solution.
- Processing: Utilize a queue to manage the courses as they are processed. Begin with courses that have no prerequisites.
- Cycle Detection: If during processing we end up revisiting a course that's only partially processed (indicating a cycle), it's concluded that it's impossible to complete all courses. In such a case, return an empty array.
- Order Construction: For each node/course processed from the queue, append it to the result array and reduce the in-degree of its neighboring nodes by one. If any neighbor’s in-degree drops to zero, it gets added to the queue indicating it has no further prerequisites remaining and is ready to be processed.
By the end of this process, if we've successfully processed all courses, the resultant array provides a valid order. If not all courses are processed (due to remaining courses having non-zero in-degree after processing, indicating cycles or unresolvable dependencies), it would mean that no valid ordering exists, thereby returning an empty array.
This approach efficiently maps out the problem using graph traversal techniques, ensuring that all constraints and prerequisites are appropriately considered. The method is computationally feasible within the given constraints, managing to find a solution or establish the impossibility of one in reasonable time.
Solutions
- C++
- Java
- Python
class Solution {
public:
vector<int> sortCourses(int totalCourses, vector<vector<int>>& deps) {
map<int, list<int>> graph;
vector<int> inDegrees(totalCourses, 0);
vector<int> courseOrder;
// Building the graph
for (const auto& dep : deps) {
int course = dep[0];
int prerequisite = dep[1];
graph[prerequisite].push_back(course);
inDegrees[course]++;
}
// Queue for courses with no prerequisites
queue<int> coursesQueue;
for (int i = 0; i < totalCourses; i++) {
if (inDegrees[i] == 0) {
coursesQueue.push(i);
}
}
// Processing the graph
while (!coursesQueue.empty()) {
int currentCourse = coursesQueue.front();
coursesQueue.pop();
courseOrder.push_back(currentCourse);
if (graph.count(currentCourse)) {
for (int neighbor : graph[currentCourse]) {
inDegrees[neighbor]--;
if (inDegrees[neighbor] == 0) {
coursesQueue.push(neighbor);
}
}
}
}
// Check if we could sort all courses
if (courseOrder.size() == totalCourses) {
return courseOrder;
}
return vector<int>();
}
};
This solution addresses the problem of scheduling courses given their dependencies, determining a possible order of courses such that all prerequisites are taken before a course. Implemented in C++, the method sortCourses
takes the totalCourses
and their dependencies deps
as parameters and returns a vector of courses in the possible order they should be taken.
- Starts by establishing a graph (
map<int, list<int>>
) to represent the dependencies and a vector (inDegrees
) to maintain the count of prerequisites for each course. - Builds the graph using input
deps
where each dependency [course
,prerequisite
] increases the in-degree ofcourse
and mapsprerequisite
tocourse
in the graph. - Initializes a queue to store the courses having no prerequisites using the
inDegrees
vector. - Processes the courses in order:
- Removes a course from the queue, adds it to
courseOrder
. - Checks its neighbors (dependent courses), decrements their in-degree, and if any dependent courses no longer have prerequisites, they are added to the queue.
- Removes a course from the queue, adds it to
- Finally, checks if all courses have been processed (the size of
courseOrder
should equaltotalCourses
):- If true, returns the
courseOrder
. - Otherwise, returns an empty vector, indicating it's not possible to complete all courses due to circular dependencies or inconsistencies.
- If true, returns the
The efficient use of graphs, queue, and adjacency list approach ensures that the algorithm can effectively handle the topological sorting of the courses based on dependencies, which is typical in scenarios like course scheduling or project task management where order based on prerequisites or dependencies is crucial.
class Solution {
public int[] courseSchedule(int totalCourses, int[][] prereqs) {
Map<Integer, List<Integer>> adjacencyList = new HashMap<>();
int[] incomingEdges = new int[totalCourses];
int[] topoSort = new int[totalCourses];
// Build the graph
for (int i = 0; i < prereqs.length; i++) {
int target = prereqs[i][0];
int prerequisite = prereqs[i][1];
List<Integer> coursesList = adjacencyList.getOrDefault(prerequisite, new ArrayList<Integer>());
coursesList.add(target);
adjacencyList.put(prerequisite, coursesList);
// Count the number of prerequisites for each course
incomingEdges[target]++;
}
// Queue for the courses with no prerequisites
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < totalCourses; i++) {
if (incomingEdges[i] == 0) {
queue.add(i);
}
}
int index = 0;
// Process each course with no prerequisites in the queue
while (!queue.isEmpty()) {
int course = queue.poll();
topoSort[index++] = course;
// Reduce the count of incoming edges for all dependent courses
if (adjacencyList.containsKey(course)) {
for (int dependentCourse : adjacencyList.get(course)) {
incomingEdges[dependentCourse]--;
// If no more prerequisites for this course, add to the queue
if (incomingEdges[dependentCourse] == 0) {
queue.offer(dependentCourse);
}
}
}
}
// If we managed to process all courses
if (index == totalCourses) {
return topoSort;
}
return new int[0]; // Return an empty array if topological sort is not possible
}
}
The problem "Course Schedule II" requires determining whether all courses can be completed given a list of prerequisites and, if possible, returning the order in which the courses should be taken. In Java, the solution involves constructing the solution class with a method courseSchedule
.
Here's the approach taken:
Graph Representation: Utilize a hashmap to represent an adjacency list of all courses and their respective prerequisites. Another array manages the count of incoming edges (prerequisites) for each course.
Graph Construction:
- Iterate over the prerequisites array, populating the adjacency list and updating the count of incoming prerequisites for each course.
Topological Sorting:
- Use a queue to maintain courses with no prerequisites (incoming edges count of zero).
- Gradually extract courses from the queue, appending them to the result array of course order and simultaneously reducing the prerequisites count for neighboring courses in the adjacency list.
- For any neighbors that no longer have prerequisites, add them to the queue for subsequent processing.
Order Determination:
- If able to process all the courses, meaning the topological sort includes all courses, return the sorted order.
- If a cycle exists (some courses still have prerequisites unmet after processing), return an empty array signifying that it's impossible to complete all courses due to cyclic dependencies.
This approach ensures that the solution efficiently determines the course order by leveraging topological sorting of a Directed Acyclic Graph (DAG), handling cases of cyclic dependencies explicitly.
from collections import defaultdict, deque
class Scheduler:
def scheduleCourses(self, totalCourses: int, preReqs: List[List[int]]) -> List[int]:
graph = defaultdict(list)
degree = {}
for course, prerequisite in preReqs:
graph[prerequisite].append(course)
degree[course] = degree.get(course, 0) + 1
zero_degree_queue = deque([i for i in range(totalCourses) if i not in degree])
course_order = []
while zero_degree_queue:
course = zero_degree_queue.popleft()
course_order.append(course)
if course in graph:
for next_course in graph[course]:
degree[next_course] -= 1
if degree[next_course] == 0:
zero_degree_queue.append(next_course)
return course_order if len(course_order) == totalCourses else []
This Python solution for determining the order of courses based on prerequisites uses the topological sorting of a directed graph, employing Kahn's algorithm.
- First, an adjacency list
graph
and an in-degreedegree
dictionary are created from the list of prerequisitespreReqs
. Each prerequisite pair updates the graph and degree of the subsequent course. - A queue
zero_degree_queue
holds all courses that have no prerequisites (i.e., non-leaf nodes in the graph). - Another list,
course_order
, is used to store the result — the order of courses to be taken. - The main loop processes courses from
zero_degree_queue
, appending them tocourse_order
and decrementing the in-degrees of their dependent courses. When a course’s in-degree reaches zero, it is added to the queue. - The function finally returns
course_order
if all the courses are covered; otherwise, it returns an empty list to indicate that the course sequence isn't possible due to cyclic dependencies.
The code is structured using a class Scheduler
with a method scheduleCourses
that takes totalCourses
and a list of prerequisite pairs. This straightforward approach ensures that the prerequisite relations are properly handled, and only valid course sequences are returned.
No comments yet.