Each course has a duration, and we can take courses in parallel as long as prerequisites are satisfied. The minimum time to finish all courses equals the longest path in the dependency graph, where path length is the sum of course durations along that path.
For each course, we need to find the maximum time required to complete it and all its dependent courses. Using DFS with memoization, we compute the total time starting from each course by taking the maximum of all paths through its dependencies, plus the course's own duration.
dfs:class Solution:
def minimumTime(self, n: int, relations: list[list[int]], time: list[int]) -> int:
adj = defaultdict(list)
for src, dst in relations:
adj[src].append(dst)
max_time = {}
def dfs(src):
if src in max_time:
return max_time[src]
res = time[src - 1]
for nei in adj[src]:
res = max(res, time[src - 1] + dfs(nei))
max_time[src] = res
return res
for i in range(1, n + 1):
dfs(i)
return max(max_time.values())Where is the number of courses and is the number of prerequisites.
The recursive DFS can be converted to an iterative version using an explicit stack. This avoids potential stack overflow for deep recursion and gives more control over the traversal order.
We use a two-phase approach: first push a node onto the stack, then when we pop it again after processing its children, we compute its final value. A "processed" flag distinguishes between these two phases.
dfs:class Solution:
def minimumTime(self, n: int, relations: list[list[int]], time: list[int]) -> int:
adj = [[] for _ in range(n)]
for src, dst in relations:
adj[src - 1].append(dst - 1)
maxTime = [-1] * n
processed = [False] * n
for i in range(n):
if maxTime[i] == -1:
stack = [i]
while stack:
node = stack.pop()
if processed[node]:
best = 0
for nei in adj[node]:
best = max(best, maxTime[nei])
maxTime[node] = time[node] + best
else:
processed[node] = True
stack.append(node)
for nei in adj[node]:
if maxTime[nei] == -1:
stack.append(nei)
return max(maxTime)Where is the number of courses and is the number of prerequisites.
Instead of working backwards from end nodes (DFS approach), we can work forwards from start nodes using topological sort. Kahn's algorithm processes nodes in dependency order, ensuring that when we process a course, all its prerequisites have already been processed.
For each course, we track the maximum time needed to reach it (including its own duration). When we process a course, we update all its dependents by propagating the maximum completion time. This naturally computes the longest weighted path through the graph.
0 to the queue.0.class Solution:
def minimumTime(self, n: int, relations: list[list[int]], time: list[int]) -> int:
adj = [[] for _ in range(n)]
indegree = [0] * n
maxTime = time[:]
for src, dst in relations:
adj[src - 1].append(dst - 1)
indegree[dst - 1] += 1
queue = deque([i for i in range(n) if indegree[i] == 0])
while queue:
node = queue.popleft()
for nei in adj[node]:
maxTime[nei] = max(maxTime[nei], maxTime[node] + time[nei])
indegree[nei] -= 1
if indegree[nei] == 0:
queue.append(nei)
return max(maxTime)Where is the number of courses and is the number of prerequisites.
Unlike the simpler version where each course takes one semester, this problem assigns different durations to each course. The answer is not just the longest path by node count, but the longest path by total time. Each course's duration must be added to the path weight.
When using topological sort, each course's initial time should be its own duration, not zero. Failing to initialize maxTime[i] = time[i] means courses with no prerequisites will incorrectly have zero completion time.
For the DFS approach, edges should point from prerequisites to dependent courses (forward direction). For some formulations, you might need to reverse this. Mixing up directions leads to computing minimum time to reach a course instead of minimum time from a course to complete all dependents.
When a course has multiple prerequisites, it cannot start until all prerequisites complete. This means taking the maximum completion time among all prerequisites, not the sum. Using addition instead of max will drastically overcount the total time.
Course numbers are often 1-indexed in the input, but the time array is 0-indexed. Accessing time[course] instead of time[course - 1] causes array index out of bounds or incorrect time values.