Time Taken to Mark All Nodes solution and explanation
5 more videos on this problem
A-D Leetcode Biweekly Contest 136 Editorials | Time Taken to Mark All Nodes | Abhinav AwasthiAbhinav Awasthi · 56:21 · 6K views
3241. Time Taken to Mark All Nodes | Biweekly Contest 136 | LeetCodePulkit Malhotra · 16:41 · 935 views
Time Taken to Mark All Nodes | Rerooting DP | Explained With Examples | C++ | Leetcode Biweekly 136CodeNow - By Gopal Gupta · 28:41 · 753 views
3241. Time Taken to Mark All Nodes (Leetcode Hard)Programming Live with Larry · 45:50 · 703 views
3241. Time Taken to Mark All Nodes || Leetcode Biweekly 136 || ExplanationGad-An-Tik · 13:41 · 552 views
3241. Time Taken to Mark All Nodes || Bi-Weekly Leetcode contest 136 || GeekstocodeGeekstocode · 28:48 · 297 views
The problem
There exists an undirected tree with n nodes numbered 0 to n - 1. You are given a 2D integer array edges of length n - 1, where edges[i] = [ui, vi] indicates that there is an edge between nodes ui and vi in the tree.
Initially, all nodes are unmarked. For each node i:
- If
iis odd, the node will get marked at timexif there is at least one node adjacent to it which was marked at timex - 1. - If
iis even, the node will get marked at timexif there is at least one node adjacent to it which was marked at timex - 2.
Return an array times where times[i] is the time when all nodes get marked in the tree, if you mark node i at time t = 0.
Note that the answer for each times[i] is independent, i.e. when you mark node i all other nodes are unmarked.
Example 1:
Input: edges = [[0,1],[0,2]]
Output: [2,4,3]
Explanation:

- For
i = 0:- Node 1 is marked at
t = 1, and Node 2 att = 2.
- Node 1 is marked at
- For
i = 1:- Node 0 is marked at
t = 2, and Node 2 att = 4.
- Node 0 is marked at
- For
i = 2:- Node 0 is marked at
t = 2, and Node 1 att = 3.
- Node 0 is marked at
Example 2:
Input: edges = [[0,1]]
Output: [1,2]
Explanation:

- For
i = 0:- Node 1 is marked at
t = 1.
- Node 1 is marked at
- For
i = 1:- Node 0 is marked at
t = 2.
- Node 0 is marked at
Example 3:
Input: edges = [[2,4],[0,1],[2,3],[0,2]]
Output: [4,6,3,5,5]
Explanation:

Constraints:
2 <= n <= 105edges.length == n - 1edges[i].length == 20 <= edges[i][0], edges[i][1] <= n - 1- The input is generated such that
edgesrepresents a valid tree.
Approach overview
Problem Overview: You are given a tree with n nodes. If a node becomes marked, it spreads the mark to its neighbors after a delay that depends on the node’s parity (odd nodes propagate faster than even nodes). For every node i, treat it as the starting node and compute how long it takes until all nodes in the tree are marked.
Approach 1: Breadth-First Search from Every Node (BFS) (Time: O(n²), Space: O(n))
Build an adjacency list for the tree and run BFS starting from each node. The BFS simulates how the mark spreads through the graph. When visiting a neighbor, add a delay based on the node’s parity (for example, +1 for odd nodes and +2 for even nodes). Track the maximum time required to reach any node during that traversal. Repeating this process for all n starting nodes produces the final answer array. This approach is straightforward and mirrors the spreading process directly, but it recomputes traversal work for every root, which leads to quadratic complexity on large trees.
Approach 2: Rerooting with Depth-First Search (DFS) + Dynamic Programming (Time: O(n), Space: O(n))
The optimal approach uses dynamic programming on a tree combined with depth-first search. First run a DFS to compute the longest time needed to reach descendants for every node (a "down" value). Each transition adds the parity-based delay for the destination node. This gives the maximum marking time within each node’s subtree.
A second DFS performs a rerooting step. When moving the root from a parent to a child, recompute the maximum distance considering both the child’s subtree and paths that go upward through the parent. Maintain the two largest child contributions so you can update values efficiently when excluding a specific branch. The final result for node i is the maximum time required to reach any node in the tree when the spread starts from i. Because each edge is processed a constant number of times, the total complexity remains linear.
Recommended for interviews: Start by explaining the BFS simulation since it directly models the marking process and shows clear understanding of graph traversal. Interviewers usually expect the rerooting DFS optimization. It reduces repeated work by turning the problem into a classic tree DP that computes distances for every possible root in O(n) time.
Approach 1: Breadth-First Search (BFS)
This approach utilizes a Breadth-First Search (BFS) traversal to determine the time it takes to mark all nodes. By starting from the node i and considering the marking rules based on node parity (odd or even), we propagate markings across the tree. Utilize a queue to manage nodes to be visited and a distance array to track marking times.
from collections import deque
def mark_all_nodes_bfs(n, edges):
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
def bfs(start):
time = [-1] * n
queue = deque([(start, 0)])
time[start] = 0
while queue:
node, t = queue.popleft()
for neighbor in graph[node]:
if time[neighbor] == -1:
if neighbor % 2 == 0:
new_time = t + 2
else:
new_time = t + 1
if time[neighbor] == -1 or new_time < time[neighbor]:
time[neighbor] = new_time
queue.append((neighbor, new_time))
return max(time)
result = []
for i in range(n):
result.append(bfs(i))
return resultfunction markAllNodesBFS(n, edges) {
const graph = Array.from({ length: n }, () => []);
for (const [u, v] of edges) {
graph[u].push(v);
graph[v].push(u);
}
function bfs(start) {
const time = Array(n).fill(-1);
const queue = [[start, 0]];
time[start] = 0;
while (queue.length) {
const [node, t] = queue.shift();
for (const neighbor of graph[node]) {
if (time[neighbor] === -1) {
const newTime = neighbor % 2 === 0 ? t + 2 : t + 1;
if (time[neighbor] === -1 || newTime < time[neighbor]) {
time[neighbor] = newTime;
queue.push([neighbor, newTime]);
}
}
}
}
return Math.max(...time);
}
const result = [];
for (let i = 0; i < n; i++) {
result.push(bfs(i));
}
return result;
}In this Python solution, we maintain a graph representation of the tree using adjacency lists. For each node, marked as the starting point, perform a BFS to calculate the time it takes to mark all other nodes considering their conditions. This way, distances from the starting node to others are incrementally updated.
Time Complexity: O(n^2) since for each node we perform a BFS which could traverse all nodes.
Space Complexity: O(n) for storing the graph and the queue in BFS.
Approach 2: Depth-First Search (DFS)
This approach uses Depth-First Search (DFS) to traverse the tree and recursively calculate the time to mark nodes based on their conditions. It ensures that nodes are visited according to their marking rules, and marking times are dynamically computed and updated.
import java.util.*;
public class Solution {
public int[] markAllNodesDFS(int n, int[][] edges) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
int[] result = new int[n];
for (int i = 0; i < n; i++) {
result[i] = dfs(i, graph, new boolean[n]);
}
return result;
}
private int dfs(int node, List<List<Integer>> graph, boolean[] visited) {
visited[node] = true;
int time = 0;
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
int neighborTime = neighbor % 2 == 0 ? dfs(neighbor, graph, visited) + 2 : dfs(neighbor, graph, visited) + 1;
time = Math.max(time, neighborTime);
}
}
visited[node] = false;
return time;
}
}#include <vector>
#include <algorithm>
std::vector<int> markAllNodesDFS(int n, const std::vector<std::vector<int>>& edges) {
std::vector<std::vector<int>> graph(n);
for (const auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
std::vector<int> result(n);
for (int i = 0; i < n; ++i) {
std::vector<bool> visited(n, false);
result[i] = dfs(i, graph, visited);
}
return result;
}
int dfs(int node, const std::vector<std::vector<int>>& graph, std::vector<bool>& visited) {
visited[node] = true;
int time = 0;
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
int neighborTime = (neighbor % 2 == 0) ? dfs(neighbor, graph, visited) + 2 : dfs(neighbor, graph, visited) + 1;
time = std::max(time, neighborTime);
}
}
visited[node] = false;
return time;
}In the Java solution, we represent the tree using adjacency lists. A DFS is performed for each node, accounting for marking rules by adjusting the timing of nodes recursively based on their even or odd indices, thus producing the maximum marking time from any given starting node.
Time Complexity: O(n^2) due to iterative DFS from each node.
Space Complexity: O(n) for storing the graph and recursion stack.
Complexity comparison
| Approach | Complexity |
|---|---|
| Breadth-First Search (BFS) | Time Complexity: O(n^2) since for each node we perform a BFS which could traverse all nodes. |
| Depth-First Search (DFS) | Time Complexity: O(n^2) due to iterative DFS from each node. |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| BFS from Every Node | O(n²) | O(n) | Useful for understanding the marking spread simulation or when n is small |
| DFS Tree DP (Subtree Distances) | O(n) | O(n) | Computes longest downward propagation times in each subtree |
| Rerooting DFS Optimization | O(n) | O(n) | Best approach for large trees; calculates results for all starting nodes efficiently |
Frequently asked questions
Is Time Taken to Mark All Nodes easy or hard?
Time Taken to Mark All Nodes is classified as a Hard problem because it combines tree traversal with rerooting dynamic programming. Recognizing that results for all roots can be computed in linear time using two DFS passes is the key challenge.
Time Taken to Mark All Nodes Python/Java solution
Python implementations commonly simulate the spread using BFS for clarity, while Java and C++ solutions often implement the optimized DFS rerooting dynamic programming approach. The optimal implementations run in O(n) time and store adjacency lists plus DP arrays.
How to solve Time Taken to Mark All Nodes in O(n)?
Use two DFS passes on the tree. The first DFS computes the maximum propagation time from each node to nodes in its subtree while accounting for the parity-based delay. The second DFS reroots the tree and updates distances so each node considers both its subtree paths and paths through its parent, producing the final answer for all nodes in linear time.
What is the best approach for Time Taken to Mark All Nodes?
The most efficient approach uses rerooting dynamic programming with depth-first search on the tree. First compute the longest propagation time inside each subtree, then reroot the tree to account for paths through the parent side. This allows calculating the marking time for every starting node in O(n) time and O(n) space.
Is Time Taken to Mark All Nodes asked at Google/Amazon/Meta?
Problems involving rerooting DP on trees and propagation across graphs frequently appear in interviews at companies like Google, Amazon, and Meta. Variants of this problem test understanding of tree dynamic programming, longest path calculations, and efficient graph traversal.
What data structure is used in Time Taken to Mark All Nodes?
The solution uses an adjacency list to represent the tree, along with DFS or BFS traversal. The optimized method also uses arrays to store dynamic programming values such as the longest propagation time in each subtree and rerooted results for every node.
What is the time complexity of Time Taken to Mark All Nodes?
The optimal solution runs in O(n) time using DFS-based tree dynamic programming with a rerooting technique. Each edge in the tree is processed a constant number of times. A straightforward BFS simulation from every node takes O(n²) time because the traversal is repeated for all possible starting nodes.
