The Parallel Courses problem asks you to determine the minimum number of semesters required to complete n
courses given a list of prerequisite relationships. Each prerequisite is a pair [prevCourse, nextCourse]
, meaning you must complete prevCourse
before taking nextCourse
. In each semester, you can take any number of courses as long as all their prerequisites have been completed in previous semesters. The task is to return the minimum number of semesters needed to finish all courses, or -1
if it's impossible due to cyclic dependencies.
1
to n
.-1
if it's not possible to finish all courses (i.e., there is a cycle).At first glance, the problem resembles the classic "Course Schedule" or "Topological Sort" challenge. The key difference is that we want to minimize the number of semesters, taking as many eligible courses as possible in each semester. A brute-force approach might try all possible combinations of course selections per semester, but this quickly becomes intractable as the number of courses increases.
Instead, we can think of the problem as finding the "longest path" in a directed acyclic graph (DAG) formed by the prerequisite relationships. Each semester corresponds to moving one level deeper in the dependency graph: in semester 1, we take all courses with no prerequisites; in semester 2, we take all courses whose prerequisites were completed in semester 1, and so on. If the graph contains a cycle, it's impossible to complete all courses.
Therefore, the solution requires:
We solve this problem using a modified topological sort (BFS) approach:
n
at the end, a cycle exists, so return -1
.This approach is efficient because each course and prerequisite is processed only once, and the BFS ensures we take as many courses as possible each semester.
Example Input:
n = 4
, prerequisites = [[2,1],[3,1],[1,4]]
Step-by-step:
Result: 3
Brute-force Approach:
O(n!)
. This is not feasible for large n
.O(n + m)
, where n
is the number of courses and m
is the number of prerequisite pairs. Each course and edge is processed once.O(n + m)
for the adjacency list and in-degree array.This makes the optimized approach suitable for large inputs.
The Parallel Courses problem is a classic application of topological sorting in a directed acyclic graph. By leveraging BFS and processing the graph layer by layer, we efficiently determine the minimum number of semesters required to complete all courses, while also detecting cycles that make completion impossible. The key insight is to take as many eligible courses in parallel as possible each semester, corresponding to the longest path in the dependency graph.
from collections import deque, defaultdict
class Solution:
def minimumSemesters(self, n, relations):
graph = defaultdict(list)
indegree = [0] * (n + 1)
for prev, nextc in relations:
graph[prev].append(nextc)
indegree[nextc] += 1
queue = deque()
for course in range(1, n + 1):
if indegree[course] == 0:
queue.append(course)
semesters = 0
taken = 0
while queue:
for _ in range(len(queue)):
course = queue.popleft()
taken += 1
for neighbor in graph[course]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)
semesters += 1
return semesters if taken == n else -1
#include <vector>
#include <queue>
using namespace std;
class Solution {
public:
int minimumSemesters(int n, vector<vector<int>>& relations) {
vector<vector<int>> graph(n + 1);
vector<int> indegree(n + 1, 0);
for (auto &rel : relations) {
graph[rel[0]].push_back(rel[1]);
indegree[rel[1]]++;
}
queue<int> q;
for (int i = 1; i <= n; ++i) {
if (indegree[i] == 0) q.push(i);
}
int semesters = 0, taken = 0;
while (!q.empty()) {
int sz = q.size();
for (int i = 0; i < sz; ++i) {
int course = q.front(); q.pop();
taken++;
for (int neighbor : graph[course]) {
indegree[neighbor]--;
if (indegree[neighbor] == 0) q.push(neighbor);
}
}
semesters++;
}
return taken == n ? semesters : -1;
}
};
import java.util.*;
class Solution {
public int minimumSemesters(int n, int[][] relations) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i <= n; i++) graph.add(new ArrayList<>());
int[] indegree = new int[n + 1];
for (int[] rel : relations) {
graph.get(rel[0]).add(rel[1]);
indegree[rel[1]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= n; i++) {
if (indegree[i] == 0) queue.offer(i);
}
int semesters = 0, taken = 0;
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
int course = queue.poll();
taken++;
for (int neighbor : graph.get(course)) {
indegree[neighbor]--;
if (indegree[neighbor] == 0) queue.offer(neighbor);
}
}
semesters++;
}
return taken == n ? semesters : -1;
}
}
/**
* @param {number} n
* @param {number[][]} relations
* @return {number}
*/
var minimumSemesters = function(n, relations) {
const graph = Array.from({length: n + 1}, () => []);
const indegree = Array(n + 1).fill(0);
for (const [prev, next] of relations) {
graph[prev].push(next);
indegree[next]++;
}
const queue = [];
for (let i = 1; i <= n; i++) {
if (indegree[i] === 0) queue.push(i);
}
let semesters = 0, taken = 0;
while (queue.length > 0) {
const sz = queue.length;
for (let i = 0; i < sz; i++) {
const course = queue.shift();
taken++;
for (const neighbor of graph[course]) {
indegree[neighbor]--;
if (indegree[neighbor] === 0) queue.push(neighbor);
}
}
semesters++;
}
return taken === n ? semesters : -1;
};