Loading problems...
TL;DR: The optimal solution utilizes Kahn's Algorithm (Topological Sort) to traverse the course dependency graph, calculating the earliest completion time for each course by tracking the maximum completion time of its prerequisites.
The "Parallel Courses III" problem asks us to determine the minimum time required to complete a set of courses. Each course has a specific duration and may have prerequisite courses that must be finished before it can begin. The critical aspect of this problem is that we can take any number of courses simultaneously (infinite parallelism). This means the time a course finishes is determined by its own duration plus the time the latest of its prerequisites finishes.
This is a classic application of finding the longest path in a Directed Acyclic Graph (DAG), which represents the critical path of the project schedule.
A naive approach to solving LeetCode 2050 is to treat the problem as a search for the longest path ending at each node using simple recursion. For every course, we could recursively calculate the time required to complete all its prerequisite chains and take the maximum.
The pseudo-code for a naive recursive approach might look like this:
function calculateTime(course):
if course has no prerequisites:
return time[course]
max_prereq_time = 0
for prev in prerequisites of course:
max_prereq_time = max(max_prereq_time, calculateTime(prev))
return max_prereq_time + time[course]
answer = 0
for i from 1 to n:
answer = max(answer, calculateTime(i))The time complexity of this approach is exponential in the worst case. Without memoization (caching results), the algorithm re-evaluates the completion time for the same shared prerequisites multiple times. In a dense graph where many courses share the same dependencies, the number of recursive calls explodes, leading to a Time Limit Exceeded (TLE) error.
The core intuition for the optimal solution lies in how we determine the start time of a specific course. If Course C depends on Course A (duration 3) and Course B (duration 5), we cannot start Course C until both A and B are finished. Since A finishes at month 3 and B finishes at month 5, Course C must wait until month 5.
Therefore, the completion time of any node is:
We need to process the nodes in an order such that when we calculate the time for node , the completion times for all its prerequisites () have already been finalized. Kahn's Algorithm guarantees this order by maintaining the in-degree (number of incoming edges) for each node.
Visual Description: Imagine the graph traversal as a wave moving from independent courses to dependent ones.

adj[u] contains all courses v such that u is a prerequisite for v.indegree of size , where indegree[i] stores the number of prerequisites for course .maxTime initialized such that maxTime[i] = time[i]. This array tracks the earliest possible month course can be completed. Initially, it assumes no prerequisites.indegree == 0 into a queue. These are the courses that can be started immediately.u.v (courses that depend on u).maxTime[v]. The new potential completion time for v is maxTime[u] + time[v]. We take the maximum of its current value and this new value: maxTime[v] = max(maxTime[v], maxTime[u] + time[v]).indegree[v].indegree[v] becomes 0, all prerequisites for v have been processed. Enqueue v.maxTime array.Let's trace the algorithm with Example 1: n = 3, relations = [[1,3],[2,3]], time = [3,2,5].
Build Graph:
maxTime array initialized to durations: [3, 2, 5] (using 1-based indexing for clarity).Initialize Queue:
[1, 2].Process Node 1:
maxTime[3] = max(5, maxTime[1] + time[3]) max(5, 3 + 5) = 8.[2].Process Node 2:
maxTime[3] = max(8, maxTime[2] + time[3]) max(8, 2 + 5) = 8.[3].Process Node 3:
[].Final Result:
maxTime is 8.The correctness relies on the invariant of Kahn's Algorithm: a node is processed (added to the queue) only when its in-degree reaches zero. In the context of this problem, "in-degree zero" implies that all prerequisite courses for the current course have been fully processed.
Because we process prerequisites first, the value maxTime[u] correctly represents the earliest finish time for prerequisite . When we update maxTime[v] = max(maxTime[v], maxTime[u] + time[v]), we are strictly adhering to the constraint that cannot start until the slowest prerequisite finishes. Since the graph is a DAG, every node will eventually reach an in-degree of 0 and be processed exactly once, ensuring the global maximum is found efficiently.
cpp#include <vector> #include <queue> #include <algorithm> using namespace std; class Solution { public: int minimumTime(int n, vector<vector<int>>& relations, vector<int>& time) { // Build adjacency list and in-degree array // Note: Input is 1-indexed, we will convert to 0-indexed logic vector<vector<int>> adj(n); vector<int> indegree(n, 0); for (const auto& rel : relations) { int prev = rel[0] - 1; int next = rel[1] - 1; adj[prev].push_back(next); indegree[next]++; } // maxTime[i] stores the earliest completion time for course i // Initialize with the course's own duration vector<int> maxTime(n); for (int i = 0; i < n; i++) { maxTime[i] = time[i]; } // Queue for Kahn's Algorithm (Topological Sort) queue<int> q; for (int i = 0; i < n; i++) { if (indegree[i] == 0) { q.push(i); } } while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) { // Update completion time for neighbor v // v can finish only after u finishes + v's own duration maxTime[v] = max(maxTime[v], maxTime[u] + time[v]); indegree[v]--; if (indegree[v] == 0) { q.push(v); } } } // The answer is the maximum completion time among all courses int result = 0; for (int t : maxTime) { result = max(result, t); } return result; } };
javaimport java.util.*; class Solution { public int minimumTime(int n, int[][] relations, int[] time) { List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } int[] indegree = new int[n]; for (int[] rel : relations) { int prev = rel[0] - 1; int next = rel[1] - 1; adj.get(prev).add(next); indegree[next]++; } // maxTime tracks completion time. Initialize with course duration. int[] maxTime = new int[n]; for (int i = 0; i < n; i++) { maxTime[i] = time[i]; } Queue<Integer> queue = new LinkedList<>(); for (int i = 0; i < n; i++) { if (indegree[i] == 0) { queue.offer(i); } } while (!queue.isEmpty()) { int u = queue.poll(); for (int v : adj.get(u)) { // Determine max time to complete v based on prerequisite u maxTime[v] = Math.max(maxTime[v], maxTime[u] + time[v]); indegree[v]--; if (indegree[v] == 0) { queue.offer(v); } } } int result = 0; for (int t : maxTime) { result = Math.max(result, t); } return result; } }
pythonfrom collections import deque, defaultdict class Solution: def minimumTime(self, n: int, relations: List[List[int]], time: List[int]) -> int: adj = defaultdict(list) indegree = [0] * n # Build graph (convert 1-indexed input to 0-indexed) for prev_course, next_course in relations: adj[prev_course - 1].append(next_course - 1) indegree[next_course - 1] += 1 # Initialize max_time with the base duration of each course max_time = list(time) queue = deque() for i in range(n): if indegree[i] == 0: queue.append(i) while queue: u = queue.popleft() for v in adj[u]: # Update neighbor's completion time max_time[v] = max(max_time[v], max_time[u] + time[v]) indegree[v] -= 1 if indegree[v] == 0: queue.append(v) return max(max_time)
Time Complexity:
Space Complexity:
indegree and maxTime arrays store elements: .The Graph BFS - Topological Sort (Kahn's Algorithm) pattern is a popular interview question strategy used in several other LeetCode problems:
Why does my solution fail with Index Out of Bounds?
time or your adjacency list), or allocate arrays of size .Why is my logic returning a smaller number than expected?
time[i] weights.max time. Ensure you initialize maxTime[i] = time[i] and update using max(current, prev_completion + duration).Why do I get Time Limit Exceeded (TLE)?
Why is the answer not just the time of the last node processed?
maxTime array, not just the last node popped from the queue.