Daily LeetCode Problems: 2050. Parallel Courses III
Navigating the Academic Expressway: Optimal Completion of Parallel Courses
Introduction:
Welcome to our daily exploration of LeetCode problems! In today’s edition, we are delving into problem 2050: “Parallel Courses III”. This problem is all about efficiently completing a set of courses with given prerequisites and durations. We will dissect the problem statement, devise a strategy to minimize the completion time, analyze the complexity, and provide a step-by-step solution.
Problem Statement:
We are presented with n courses labeled from 1 to n. Additionally, we are given a 2D integer array relations
where relations[j] = [prevCoursej, nextCoursej]
signifies that course prevCoursej
must be completed before course nextCoursej
. We also have an array time
where time[i]
indicates the time (in months) needed to complete the (i+1)th course. Our objective is to determine the minimum number of months needed to complete all the courses while adhering to the prerequisite relationships.
Approach:
To solve this problem efficiently, we’ll utilize a topological sorting approach and dynamic programming. We’ll traverse the courses in a topological order and calculate the minimum time needed to complete each course. We’ll use a memoization table to store the minimum time for each course, considering the time taken to complete its prerequisites.
Pseudocode:
def minNumberOfSemesters(n, dependencies, k):
# ... [function implementation] ...
# Function to calculate the minimum time for each course
def calculate_times(course, prereq, times, dp, k):
if dp[course] != -1:
return dp[course]
# Calculate the time for taking the course alone
max_time = times[course - 1]
# Calculate time considering prerequisites
if prereq[course]:
mask = 0
for pre_course in prereq[course]:
mask |= 1 << (pre_course - 1)
subsets = mask
while subsets > 0:
if bin(subsets).count('1') <= k:
valid = True
time_sum = 0
for i in range(n):
if subsets & (1 << i):
time_sum += times[i]
if (subsets & mask) == mask:
valid = False
break
if valid:
max_time = min(max_time, time_sum + calculate_times(course, prereq, times, dp, k) + 1)
# Get the next valid subset
subsets = (subsets - 1) & mask
dp[course] = max_time
return max_time
Complexity Analysis:
Let’s analyze the complexity of our solution:
- Time complexity: The main function involves traversing the courses, and calculating the minimum time for each course, resulting in a time complexity of O(n + e + n * 2^n) where n is the number of courses, and e is the number of prerequisites.
- Space complexity: We use a memoization table and auxiliary data structures to store course information, resulting in a space complexity of O(n + e).
Step-by-Step Solution:
- Traverse the courses and build a graph representing the prerequisite relationships.
- Perform a topological sort on the graph to get a valid course order.
- Calculate the minimum time needed for each course considering its prerequisites.
- Return the maximum time needed among all the courses.
Full Solution
Python
class Solution:
def minimumTime(self, n: int, relations: List[List[int]], time: List[int]) -> int:
graph = [[] for _ in range(n)]
inDegree = [0] * n
dist = time.copy()
# Build graph.
for a, b in relations:
u = a - 1
v = b - 1
graph[u].append(v)
inDegree[v] += 1
# Topology
q = collections.deque([i for i, d in enumerate(inDegree) if d == 0])
while q:
u = q.popleft()
for v in graph[u]:
dist[v] = max(dist[v], dist[u] + time[v])
inDegree[v] -= 1
if inDegree[v] == 0:
q.append(v)
return max(dist)
Java
class Solution {
public int minimumTime(int n, int[][] relations, int[] time) {
List<Integer>[] graph = new List[n];
int[] inDegree = new int[n];
int[] dist = time.clone();
for (int i = 0; i < n; ++i)
graph[i] = new ArrayList<>();
// Build graph.
for (int[] r : relations) {
final int u = r[0] - 1;
final int v = r[1] - 1;
graph[u].add(v);
++inDegree[v];
}
// Topology
Queue<Integer> q = IntStream.range(0, n)
.filter(i -> inDegree[i] == 0)
.boxed()
.collect(Collectors.toCollection(ArrayDeque::new));
while (!q.isEmpty()) {
final int u = q.poll();
for (final int v : graph[u]) {
dist[v] = Math.max(dist[v], dist[u] + time[v]);
if (--inDegree[v] == 0)
q.offer(v);
}
}
return Arrays.stream(dist).max().getAsInt();
}
}
C++
class Solution {
public:
int minimumTime(int n, vector<vector<int>>& relations, vector<int>& time) {
vector<vector<int>> graph(n);
vector<int> inDegree(n);
queue<int> q;
vector<int> dist(time);
// Build graph.
for (const vector<int>& r : relations) {
const int u = r[0] - 1;
const int v = r[1] - 1;
graph[u].push_back(v);
++inDegree[v];
}
// Topology
for (int i = 0; i < n; ++i)
if (inDegree[i] == 0)
q.push(i);
while (!q.empty()) {
const int u = q.front();
q.pop();
for (const int v : graph[u]) {
dist[v] = max(dist[v], dist[u] + time[v]);
if (--inDegree[v] == 0)
q.push(v);
}
}
return ranges::max(dist);
}
};
Conclusion
In this article, we delved into problem 2050, “Parallel Courses III”, by strategizing the optimal completion of courses with prerequisites and durations. By employing topological sorting and dynamic programming, we were able to efficiently calculate the minimum time needed for each course while considering its prerequisites. Understanding the problem statement and following our approach will enable you to navigate the academic expressway and complete courses in the most efficient manner. Happy learning!