Daily LeetCode Problems: 2050. Parallel Courses III

Navigating the Academic Expressway: Optimal Completion of Parallel Courses

Monit Sharma
4 min readOct 18, 2023

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:

  1. Traverse the courses and build a graph representing the prerequisite relationships.
  2. Perform a topological sort on the graph to get a valid course order.
  3. Calculate the minimum time needed for each course considering its prerequisites.
  4. 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!

--

--