1522. Diameter of N-Ary Tree 🔒
Problem Description
You are given the root of an N-ary tree (a tree where each node can have any number of children, not just two like in a binary tree). Your task is to find the diameter of this tree.
The diameter of an N-ary tree is defined as the length of the longest path between any two nodes in the tree. This path is measured by counting the number of edges between the two nodes.
Key points to understand:
- The longest path may or may not pass through the root node
- The path connects any two nodes in the tree
- The length is counted as the number of edges, not the number of nodes
- An N-ary tree node has a value and a list of children (which can be empty for leaf nodes)
For example, if you have a tree where the longest path goes from one leaf node, up through some parent nodes, and then down to another leaf node spanning 5 edges total, then the diameter would be 5.
The tree structure is defined with a Node
class where:
val
represents the node's valuechildren
is a list containing all child nodes
The input tree is serialized using level-order traversal, where groups of children are separated by null values, but you'll be working directly with the Node
object structure.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: An N-ary tree is a special type of graph where nodes are connected by edges, and there's a hierarchical structure with a root node and children.
Is it a tree?
- Yes: The problem explicitly states we're working with an N-ary tree structure, which is a connected acyclic graph with a root node where each node can have multiple children.
DFS
- We arrive at DFS (Depth-First Search) as our algorithm choice.
Conclusion: The flowchart suggests using a DFS approach for finding the diameter of the N-ary tree.
This makes perfect sense because:
- We need to explore the depth of each subtree to find the longest paths
- For each node, we need to calculate the height of all its subtrees
- The diameter at any node could be the sum of the two longest paths among its children
- DFS allows us to process nodes bottom-up, calculating heights and updating the diameter as we traverse back up the tree
The DFS pattern is ideal here as it naturally computes the height of subtrees while simultaneously tracking the maximum diameter found so far by considering paths that pass through each node.
Intuition
To find the diameter of an N-ary tree, we need to think about what constitutes the longest path between any two nodes. The key insight is that the longest path in a tree will always be between two leaf nodes (or a leaf and another node), and this path will have a "peak" - a highest node where the path changes direction from going up to going down.
For any node in the tree, the longest path that passes through it would be formed by:
- Going down to the deepest leaf in one of its subtrees
- Going down to the deepest leaf in another one of its subtrees
This means at each node, we need to know the heights of all its subtrees. The diameter passing through that node would be the sum of the two largest heights among its children.
Here's the critical observation: as we traverse the tree using DFS, we can:
- Calculate the height of each subtree (which is
1 + maximum height among all children
) - Keep track of the two largest heights (
m1
andm2
) among all children at each node - Update our global maximum diameter with
m1 + m2
at each node
Why do we need only the top two heights? Because a path can only go down through at most two different children of any node - one path going down through one child and another path going down through a different child.
The beauty of this approach is that with a single DFS traversal, we simultaneously:
- Compute heights bottom-up (needed for parent calculations)
- Track the maximum diameter seen so far (our answer)
Each node returns its height to its parent while potentially updating the global diameter, making this an efficient O(n) solution where we visit each node exactly once.
Learn more about Tree and Depth-First Search patterns.
Solution Approach
The implementation uses a recursive DFS approach with the following key components:
Main Structure:
- We define a helper function
dfs(root)
that returns the height of the subtree rooted at the given node - We maintain a global variable
ans
to track the maximum diameter found so far
DFS Function Logic:
-
Base Case: If the node is
None
, return height 0 -
Process Each Child:
- We maintain two variables
m1
andm2
to track the two largest heights among all children m1
stores the maximum height,m2
stores the second maximum height- Initially, both are set to 0
- We maintain two variables
-
Height Collection and Tracking:
for child in root.children: t = dfs(child) # Get height of child's subtree if t > m1: m2, m1 = m1, t # New maximum found, shift m1 to m2 elif t > m2: m2 = t # New second maximum found
This clever updating ensures we always have the top two heights without needing to sort or use additional space.
-
Update Global Diameter:
ans = max(ans, m1 + m2)
The diameter passing through the current node is the sum of the two longest paths going down through its children.
-
Return Height:
return 1 + m1
The height of the current subtree is 1 (for the current node) plus the maximum height among all its children.
Key Insights:
- The
nonlocal ans
declaration allows the nested function to modify the outer scope'sans
variable - The algorithm visits each node exactly once, giving us O(n) time complexity
- Space complexity is O(h) where h is the height of the tree, due to the recursion call stack
The elegance of this solution lies in how it simultaneously computes two things: the height of each subtree (needed for parent calculations) and the maximum diameter (our final answer), all in a single tree traversal.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small N-ary tree example to illustrate how the solution works:
1 /|\ 3 2 4 / \ 5 6
In this tree:
- Node 1 has three children: 3, 2, and 4
- Node 3 has two children: 5 and 6
- Nodes 2, 4, 5, and 6 are leaf nodes
Step-by-step DFS traversal:
- Start DFS from root (node 1)
- Process child 3:
- Process child 5 (leaf): returns height 1
- Process child 6 (leaf): returns height 1
- At node 3: m1=1, m2=1
- Update diameter: ans = max(0, 1+1) = 2
- Return height: 1 + 1 = 2
- Process child 3:
- Back at root (node 1), continue with child 2:
- Node 2 (leaf): returns height 1
- Current state at root: m1=2 (from node 3), m2=1 (from node 2)
- Process child 4:
- Node 4 (leaf): returns height 1
- Current state at root: m1=2, m2=1 (node 4's height doesn't change m2)
- Final calculation at root:
- m1=2 (height through node 3), m2=1 (height through node 2 or 4)
- Update diameter: ans = max(2, 2+1) = 3
- Return height: 1 + 2 = 3
Result: The diameter is 3, representing the longest path from node 5 → 3 → 1 → 2 (or 4), which has 3 edges.
The algorithm correctly identifies that the longest path passes through the root, connecting the deepest leaf in the left subtree (node 5 or 6) with a leaf in another subtree (node 2 or 4). The key insight is how at each node, we track the two largest heights among children and update the global diameter with their sum.
Solution Implementation
1"""
2# Definition for a Node.
3class Node:
4 def __init__(self, val=None, children=None):
5 self.val = val
6 self.children = children if children is not None else []
7"""
8
9
10class Solution:
11 def diameter(self, root: 'Node') -> int:
12 """
13 Find the diameter of an N-ary tree.
14 The diameter is the longest path between any two nodes in the tree.
15
16 :type root: 'Node'
17 :rtype: int
18 """
19
20 def dfs(node: 'Node') -> int:
21 """
22 Depth-first search to find the maximum depth from current node.
23 Also updates the global diameter during traversal.
24
25 :param node: Current node being processed
26 :return: Maximum depth from this node to any leaf
27 """
28 if node is None:
29 return 0
30
31 # Track the two longest paths from current node through its children
32 max_depth_1 = 0 # Longest path
33 max_depth_2 = 0 # Second longest path
34
35 # Process each child and find the two longest paths
36 for child in node.children:
37 child_depth = dfs(child)
38
39 if child_depth > max_depth_1:
40 # Found a new longest path, shift previous longest to second
41 max_depth_2 = max_depth_1
42 max_depth_1 = child_depth
43 elif child_depth > max_depth_2:
44 # Found a new second longest path
45 max_depth_2 = child_depth
46
47 # Update global diameter: sum of two longest paths through current node
48 nonlocal max_diameter
49 max_diameter = max(max_diameter, max_depth_1 + max_depth_2)
50
51 # Return the depth from current node (1 + longest path to leaf)
52 return 1 + max_depth_1
53
54 # Initialize the maximum diameter
55 max_diameter = 0
56
57 # Start DFS from root
58 dfs(root)
59
60 return max_diameter
61
1/*
2// Definition for a Node.
3class Node {
4 public int val;
5 public List<Node> children;
6
7 public Node() {
8 children = new ArrayList<Node>();
9 }
10
11 public Node(int _val) {
12 val = _val;
13 children = new ArrayList<Node>();
14 }
15
16 public Node(int _val,ArrayList<Node> _children) {
17 val = _val;
18 children = _children;
19 }
20};
21*/
22
23class Solution {
24 // Variable to store the maximum diameter found
25 private int maxDiameter;
26
27 /**
28 * Calculates the diameter of an N-ary tree.
29 * The diameter is the longest path between any two nodes in the tree.
30 *
31 * @param root The root node of the N-ary tree
32 * @return The diameter of the tree
33 */
34 public int diameter(Node root) {
35 maxDiameter = 0;
36 calculateHeight(root);
37 return maxDiameter;
38 }
39
40 /**
41 * Performs depth-first search to calculate the height of each subtree
42 * while updating the maximum diameter.
43 *
44 * @param node The current node being processed
45 * @return The height of the subtree rooted at the current node
46 */
47 private int calculateHeight(Node node) {
48 // Base case: null node has height 0
49 if (node == null) {
50 return 0;
51 }
52
53 // Track the two largest heights among all children
54 int largestHeight = 0;
55 int secondLargestHeight = 0;
56
57 // Process each child and update the two largest heights
58 for (Node child : node.children) {
59 int childHeight = calculateHeight(child);
60
61 if (childHeight > largestHeight) {
62 // New largest height found, shift previous largest to second
63 secondLargestHeight = largestHeight;
64 largestHeight = childHeight;
65 } else if (childHeight > secondLargestHeight) {
66 // New second largest height found
67 secondLargestHeight = childHeight;
68 }
69 }
70
71 // Update the maximum diameter: sum of two longest paths through current node
72 maxDiameter = Math.max(maxDiameter, largestHeight + secondLargestHeight);
73
74 // Return height of current subtree (1 + height of tallest child)
75 return 1 + largestHeight;
76 }
77}
78
1/*
2// Definition for a Node.
3class Node {
4public:
5 int val;
6 vector<Node*> children;
7
8 Node() {}
9
10 Node(int _val) {
11 val = _val;
12 }
13
14 Node(int _val, vector<Node*> _children) {
15 val = _val;
16 children = _children;
17 }
18};
19*/
20
21class Solution {
22public:
23 int maxDiameter; // Stores the maximum diameter found
24
25 /**
26 * Calculates the diameter of an N-ary tree
27 * The diameter is the length of the longest path between any two nodes
28 * @param root: Root node of the N-ary tree
29 * @return: The diameter of the tree
30 */
31 int diameter(Node* root) {
32 maxDiameter = 0;
33 calculateHeight(root);
34 return maxDiameter;
35 }
36
37private:
38 /**
39 * DFS helper function to calculate the height of each subtree
40 * While calculating height, also updates the maximum diameter
41 * @param node: Current node being processed
42 * @return: Height of the subtree rooted at current node
43 */
44 int calculateHeight(Node* node) {
45 // Base case: null node has height 0
46 if (!node) {
47 return 0;
48 }
49
50 // Track the two longest paths from children
51 int longestPath = 0;
52 int secondLongestPath = 0;
53
54 // Process all children to find the two longest paths
55 for (Node* child : node->children) {
56 int childHeight = calculateHeight(child);
57
58 // Update the two longest paths
59 if (childHeight > longestPath) {
60 // New longest path found, previous longest becomes second longest
61 secondLongestPath = longestPath;
62 longestPath = childHeight;
63 } else if (childHeight > secondLongestPath) {
64 // Update second longest path
65 secondLongestPath = childHeight;
66 }
67 }
68
69 // Update maximum diameter: sum of two longest paths through current node
70 maxDiameter = max(maxDiameter, longestPath + secondLongestPath);
71
72 // Return height of current subtree (1 + longest path from children)
73 return 1 + longestPath;
74 }
75};
76
1// Definition for a Node
2class Node {
3 val: number;
4 children: Node[];
5
6 constructor(val?: number, children?: Node[]) {
7 this.val = (val === undefined ? 0 : val);
8 this.children = (children === undefined ? [] : children);
9 }
10}
11
12// Global variable to store the maximum diameter found
13let maxDiameter: number;
14
15/**
16 * Calculates the diameter of an N-ary tree
17 * The diameter is the length of the longest path between any two nodes
18 * @param root - Root node of the N-ary tree
19 * @return The diameter of the tree
20 */
21function diameter(root: Node | null): number {
22 // Initialize the maximum diameter to 0
23 maxDiameter = 0;
24
25 // Start DFS traversal to calculate heights and update diameter
26 calculateHeight(root);
27
28 // Return the maximum diameter found
29 return maxDiameter;
30}
31
32/**
33 * DFS helper function to calculate the height of each subtree
34 * While calculating height, also updates the maximum diameter
35 * @param node - Current node being processed
36 * @return Height of the subtree rooted at current node
37 */
38function calculateHeight(node: Node | null): number {
39 // Base case: null node has height 0
40 if (!node) {
41 return 0;
42 }
43
44 // Track the two longest paths from children
45 let longestPath: number = 0;
46 let secondLongestPath: number = 0;
47
48 // Process all children to find the two longest paths
49 for (const child of node.children) {
50 // Recursively calculate the height of each child subtree
51 const childHeight: number = calculateHeight(child);
52
53 // Update the two longest paths
54 if (childHeight > longestPath) {
55 // New longest path found, previous longest becomes second longest
56 secondLongestPath = longestPath;
57 longestPath = childHeight;
58 } else if (childHeight > secondLongestPath) {
59 // Update second longest path only
60 secondLongestPath = childHeight;
61 }
62 }
63
64 // Update maximum diameter: sum of two longest paths through current node
65 // This represents the longest path that passes through the current node
66 maxDiameter = Math.max(maxDiameter, longestPath + secondLongestPath);
67
68 // Return height of current subtree (1 + longest path from children)
69 // Height includes the current node plus the longest path below it
70 return 1 + longestPath;
71}
72
Time and Space Complexity
Time Complexity: O(n)
where n
is the total number of nodes in the N-ary tree.
The algorithm performs a depth-first search (DFS) traversal of the tree, visiting each node exactly once. At each node, we iterate through all its children to compute the two longest paths. While the number of children per node can vary, each edge in the tree is traversed exactly once (from parent to child). Since a tree with n
nodes has n-1
edges, and we perform constant-time operations for updating m1
, m2
, and ans
at each node, the overall time complexity is O(n)
.
Space Complexity: O(h)
where h
is the height of the tree.
The space complexity is determined by the recursive call stack during the DFS traversal. In the worst case, when the tree is completely unbalanced (resembling a linked list), the height h
equals n
, resulting in O(n)
space complexity. In the best case, when the tree is balanced, the height would be O(log n)
for a binary tree structure, though for an N-ary tree with varying branching factors, this can differ. The algorithm uses only a constant amount of extra space for variables (ans
, m1
, m2
, t
) at each recursive level, so the dominant factor is the recursion stack depth.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing Height vs. Diameter Calculation
Pitfall: A common mistake is thinking that the diameter is simply the height of the tree or twice the height. Developers often return 2 * height
or just the height
instead of properly tracking the sum of the two longest paths through each node.
Wrong approach:
def diameter(self, root):
def dfs(node):
if not node:
return 0
max_height = 0
for child in node.children:
max_height = max(max_height, dfs(child))
return 1 + max_height
return dfs(root) # Wrong! This returns height, not diameter
Solution: Remember that diameter is the longest path between ANY two nodes, which might pass through any intermediate node, not necessarily the root. Always track max_depth_1 + max_depth_2
at each node.
2. Forgetting to Handle Trees with 0 or 1 Child
Pitfall: When a node has only one child or no children, the logic for finding two maximum heights can fail if not handled properly. Some implementations assume there are always at least two children.
Wrong approach:
for child in node.children:
heights.append(dfs(child))
heights.sort(reverse=True)
max_diameter = max(max_diameter, heights[0] + heights[1]) # IndexError if < 2 children
Solution: Initialize both max_depth_1
and max_depth_2
to 0. This naturally handles cases with fewer than 2 children since unused variables remain 0.
3. Using Global Variables Incorrectly
Pitfall: Forgetting the nonlocal
keyword when modifying the outer scope variable, or accidentally creating a local variable with the same name.
Wrong approach:
def diameter(self, root):
max_diameter = 0
def dfs(node):
# Missing 'nonlocal' declaration
max_diameter = max(max_diameter, m1 + m2) # UnboundLocalError
Solution: Always use nonlocal max_diameter
inside the nested function before modifying it, or use a mutable container like a list: max_diameter = [0]
and update with max_diameter[0] = max(...)
.
4. Incorrect Edge Counting
Pitfall: Counting nodes instead of edges when calculating the diameter, leading to an off-by-one error.
Wrong approach:
# Returning node count instead of edge count return 2 + max_depth_1 + max_depth_2 # Wrong! Adds extra nodes
Solution: The diameter is the number of edges, not nodes. When combining two paths through a node, simply add them: max_depth_1 + max_depth_2
. Each path already represents the edge count from that node downward.
5. Not Updating max_depth_2 Correctly
Pitfall: Only tracking the maximum depth and forgetting to properly maintain the second maximum, or updating them in the wrong order.
Wrong approach:
for child in node.children: child_depth = dfs(child) if child_depth > max_depth_1: max_depth_1 = child_depth # Lost the previous max_depth_1! elif child_depth > max_depth_2: max_depth_2 = child_depth
Solution: When finding a new maximum, always shift the old maximum to become the second maximum before updating: max_depth_2, max_depth_1 = max_depth_1, child_depth
.
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!