Talentd
FleetCode
Make and follow sheets.Sign in

Finish Time of Tasks I solution and explanation

MediumArrayDepth-First SearchDynamic ProgrammingTree6 min read60.3% acceptanceUpdated 27 Jul 2026
Leetcode 3965 | Finish Time of Tasks I | Clear Explanation | Leetcode biweekly contest 185CodeWithMeGuys · 19:12 · 136 viewsWatch on YouTube
4 more videos on this problem
  1. Leetcode 3965 | Finish Time of Tasks I | Clear Explanation | Leetcode biweekly contest 185CodeWithMeGuys · 19:12 · 136 views
  2. Leetcode 3965 Biweekly Contest 185 Q3 | Finish Time of Tasks I | DFS on Tree 🔥CodeSprint · 14:48 · 109 views
  3. Leetcode 3965 | Finish Time of Tasks I | DFS + Dry Run | Medium | Biweekly Contest 185Vijay Algorithms · 20:35 · 84 views
  4. LeetCode 3965: Finish Time of Tasks I | C++ DFS SolutionAeXel · 12:31 · 43 views
  5. 3965. Finish Time of Tasks I Kahn Algo | bfs | Java | QueueLeetcoding with Asmit Srivastava · 29:32 · 16 views

The problem

You are given an integer n representing the number of tasks in a project, numbered from 0 to n - 1. These tasks are connected as a tree rooted at task 0. This is represented by a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates that task ui is the parent of task vi.

You are also given an array baseTime of length n, where baseTime[i] represents the time to complete task i.

The finish time of each task is calculated as follows:

  • Leaf task: The finish time is baseTime[i].
  • Non-leaf task:
    • Let earliest be the minimum finish time among its children, and latest be the maximum finish time among its children.
    • Let ownDuration be (latest - earliest) + baseTime[i].
    • The finish time of task i is latest + ownDuration.

Return the finish time of the root task 0.

Example 1:

Input: n = 3, edges = [[0,1],[1,2]], baseTime = [9,5,3]

Output: 17

Explanation:

0 9 1 5 2 3
  • Task 2 is a leaf, so its finish time is baseTime[2] = 3.
  • Task 1 has one child task 2:
    • earliest = latest = 3
    • ownDuration = (latest - earliest) + baseTime[1] = 5
    • Finish time of task 1 is 3 + 5 = 8
  • Task 0 has one child with finish time 8:
    • earliest = latest = 8
    • ownDuration = (latest - earliest) + baseTime[0] = 9
    • Finish time of task 0 is 8 + 9 = 17

Example 2:

Input: n = 3, edges = [[0,1],[0,2]], baseTime = [4,7,6]

Output: 12

Explanation:

0 4 1 7 2 6
  • Task 1 is a leaf, so its finish time is baseTime[1] = 7.
  • Task 2 is a leaf, so its finish time is baseTime[2] = 6.
  • Task 0 has two children with finish times 7 and 6:
    • earliest = 6, latest = 7
    • ownDuration = (latest - earliest) + baseTime[0] = (7 - 6) + 4 = 5
    • Finish time of task 0 is latest + ownDuration = 7 + 5 = 12

Example 3:

Input: n = 4, edges = [[0,1],[0,2],[2,3]], baseTime = [5,8,2,1]

Output: 18

Explanation:

  • Task 1 is a leaf, so its finish time is baseTime[1] = 8.
  • Task 3 is a leaf, so its finish time is baseTime[3] = 1.
  • Task 2 has one child task 3:
    • earliest = latest = 1
    • ownDuration = (latest - earliest) + baseTime[2] = 0 + 2 = 2
    • Finish time of task 2 is latest + ownDuration = 1 + 2 = 3
  • Task 0 has two children with finish times 8 and 3:
    • earliest = 3, latest = 8
    • ownDuration = (latest - earliest) + baseTime[0] = (8 - 3) + 5 = 10
    • Finish time of task 0 is latest + ownDuration = 8 + 10 = 18

Constraints:

  • 1 <= n <= 105
  • edges.length = n - 1
  • edges[i] == [ui, vi]
  • 0 <= ui, vi <= n - 1
  • ui != vi
  • The input is generated such that edges represents a valid tree.
  • baseTime.length == n
  • 1 <= baseTime[i] <= 105​​​​​​​

Approach overview

Problem Overview: You are given tasks with a start time and a processing duration. Only one task can run at a time. If the processor is idle, it waits for the next task’s start time; otherwise the task starts when the previous one finishes. The goal is to compute the exact finish time for each task.

Approach 1: Time Simulation (Brute Force) (Time: O(T), Space: O(1))

The most literal way is to simulate time unit by unit. Maintain a pointer to the current task and increment a global clock. When the clock reaches a task’s start time, begin processing it and keep advancing the clock until its duration completes. Record the finish time, then move to the next task. This works but becomes inefficient if time values are large because the algorithm iterates across every intermediate timestamp. It demonstrates the mechanics clearly but is rarely acceptable in interviews.

Approach 2: Greedy Running Clock (Optimal) (Time: O(n), Space: O(1))

Instead of advancing time step by step, jump directly to meaningful events. Track a variable currentTime representing when the processor becomes free. For each task, compute its actual start time as max(currentTime, start[i]). The finish time is startActual + duration[i]. Update currentTime to this finish time and continue. This greedy simulation works because tasks are processed in order and the machine can only handle one job at a time, so the only state that matters is when the previous task finished.

This approach effectively compresses idle periods and processing intervals into a single calculation. Each task requires constant work: a comparison, addition, and assignment. The result is linear time with constant extra memory, making it suitable even when the number of tasks is large.

Conceptually this falls under greedy algorithms and simulation. The input handling typically uses simple array traversal, and no auxiliary data structures are required.

Recommended for interviews: The greedy running clock approach. Interviewers expect you to recognize that explicit time simulation is unnecessary. Showing the brute-force idea first demonstrates understanding of the system behavior, while the O(n) event-based simulation shows the optimization skill they are actually evaluating.

Solution

First, build the tree from the edge list edges and store each node's children in an adjacency list g.

Then perform DFS starting from the root node 0. Define a function dfs(i) that returns the finish time of task i:

  • If i is a leaf node, return baseTime[i] directly;
  • Otherwise, recursively compute the finish times of all children, and let earliest and latest be the minimum and maximum among them;
  • The own duration of the current task is ownDuration = (latest - earliest) + baseTime[i];
  • The finish time of task i is latest + ownDuration.

The answer is dfs(0).

The time complexity is O(n), and the space complexity is O(n), where n is the number of nodes.

class Solution:
    def finishTime(self, n: int, edges: List[List[int]], baseTime: List[int]) -> int:
        def dfs(i: int) -> int:
            if not g[i]:
                return baseTime[i]
            earliest, latest = inf, -inf
            for j in g[i]:
                a = dfs(j)
                earliest = min(earliest, a)
                latest = max(latest, a)
            own_duration = (latest - earliest) + baseTime[i]
            return latest + own_duration

        g = [[] for _ in range(n)]
        for u, v in edges:
            g[u].append(v)
        return dfs(0)
class Solution {
    List<Integer>[] g;
    int[] baseTime;

    long dfs(int i) {
        if (g[i].isEmpty()) {
            return baseTime[i];
        }

        long earliest = Long.MAX_VALUE / 4;
        long latest = Long.MIN_VALUE / 4;

        for (int j : g[i]) {
            long a = dfs(j);
            earliest = Math.min(earliest, a);
            latest = Math.max(latest, a);
        }

        long ownDuration = (latest - earliest) + baseTime[i];
        return latest + ownDuration;
    }

    public long finishTime(int n, int[][] edges, int[] baseTime) {
        this.baseTime = baseTime;
        g = new ArrayList[n];
        Arrays.setAll(g, k -> new ArrayList<>());

        for (int[] e : edges) {
            g[e[0]].add(e[1]);
        }

        return dfs(0);
    }
}
class Solution {
public:
    long long finishTime(int n, vector<vector<int>>& edges, vector<int>& baseTime) {
        vector<vector<int>> g(n);

        for (auto& e : edges) {
            g[e[0]].push_back(e[1]);
        }

        auto dfs = [&](this auto&& dfs, int i) -> long long {
            if (g[i].empty()) {
                return baseTime[i];
            }

            long long earliest = LLONG_MAX / 4;
            long long latest = -LLONG_MAX / 4;

            for (int j : g[i]) {
                long long a = dfs(j);
                earliest = min(earliest, a);
                latest = max(latest, a);
            }

            long long own_duration = (latest - earliest) + baseTime[i];
            return latest + own_duration;
        };

        return dfs(0);
    }
};
function finishTime(n: number, edges: number[][], baseTime: number[]): number {
    const g: number[][] = Array.from({ length: n }, () => []);

    for (const [u, v] of edges) {
        g[u].push(v);
    }

    const dfs = (i: number): number => {
        if (g[i].length === 0) {
            return baseTime[i];
        }

        let earliest = Number.MAX_SAFE_INTEGER;
        let latest = -Number.MAX_SAFE_INTEGER;

        for (const j of g[i]) {
            const a = dfs(j);
            earliest = Math.min(earliest, a);
            latest = Math.max(latest, a);
        }

        const ownDuration = latest - earliest + baseTime[i];
        return latest + ownDuration;
    };

    return dfs(0);
}
func finishTime(n int, edges [][]int, baseTime []int) int64 {
	g := make([][]int, n)

	for _, e := range edges {
		g[e[0]] = append(g[e[0]], e[1])
	}

	var dfs func(int) int64

	dfs = func(i int) int64 {
		if len(g[i]) == 0 {
			return int64(baseTime[i])
		}

		var INF int64 = 1 << 62
		var earliest int64 = INF
		var latest int64 = -INF

		for _, j := range g[i] {
			a := dfs(j)
            earliest = min(earliest, a)
            latest = max(latest, a)
		}

		ownDuration := (latest - earliest) + int64(baseTime[i])
		return latest + ownDuration
	}

	return dfs(0)
}
Try this approach in the editor

Complexity analysis

ApproachTimeSpaceWhen to Use
Time Simulation (Brute Force)O(T)O(1)Useful for understanding the process or when timestamps are very small
Greedy Running ClockO(n)O(1)General case; optimal solution for large task lists

Frequently asked questions

Is Finish Time of Tasks I easy or hard?

The problem is typically rated Medium because it tests scheduling intuition rather than complex data structures. The main challenge is recognizing that explicit time simulation is unnecessary and replacing it with a greedy running clock calculation.

Finish Time of Tasks I Python/Java solution

Both Python and Java implementations follow the same logic: iterate through tasks, compute actual start time with max(currentTime, start[i]), then update finish = start + duration. Append the finish time to the result array and update currentTime. The code runs in O(n) time with constant extra memory.

How to solve Finish Time of Tasks I in O(n)?

Iterate through the tasks once while maintaining a variable currentTime. For each task i, compute startActual = max(currentTime, start[i]), then finish = startActual + duration[i]. Store the finish time and update currentTime to that value. This avoids step‑by‑step time simulation and keeps the runtime linear.

What is the best approach for Finish Time of Tasks I?

The optimal approach is a greedy simulation using a running clock. Maintain a variable for when the processor becomes free. For each task, start at max(currentTime, start[i]) and compute finish = start + duration. This processes all tasks in O(n) time and O(1) space.

Is Finish Time of Tasks I asked at Google/Amazon/Meta?

Problems based on task scheduling and event simulation appear frequently in interviews at companies like Amazon and Google. Variations include single-thread scheduling, CPU task ordering, and interval processing. The core idea of maintaining a running processing time is commonly tested.

What data structure is used in Finish Time of Tasks I?

The solution mainly relies on array traversal and a single variable tracking the current processor time. No advanced data structure such as heaps or hash maps is required because tasks are processed sequentially.

What is the time complexity of Finish Time of Tasks I?

The optimal algorithm runs in O(n) time where n is the number of tasks. Each task requires a constant number of operations: a max comparison and an addition. Space complexity remains O(1) because only a running timestamp variable is stored.

Comments

Now write it yourselfClose this page and solve Finish Time of Tasks I in the editor, without looking.
Practise Finish Time of Tasks I