LeetCode 429 - N-ary Tree Level Order Traversal
The problem asks us to perform a level order traversal of an n-ary tree. In other words, we need to return the values of the tree nodes grouped by their depth. The root node represents level 0, its immediate children are level 1, their children are level 2, and so on.
Difficulty: 🟡 Medium
Topics: Tree, Breadth-First Search
Solution
Problem Understanding
The problem asks us to perform a level order traversal of an n-ary tree. In other words, we need to return the values of the tree nodes grouped by their depth. The root node represents level 0, its immediate children are level 1, their children are level 2, and so on.
The input is an n-ary tree represented by nodes where each node can have zero or more children. The tree is serialized using a level order format where null separates groups of children. The output is a list of lists, where each inner list contains the values of nodes at a particular depth.
Constraints provide useful information: the height of the tree is at most 1000, and there can be up to 10,000 nodes. This means the algorithm needs to handle relatively deep trees but does not require handling extremely wide trees beyond 10,000 nodes.
Important edge cases include an empty tree (root is None), a tree where some nodes have no children, or a tree that is heavily skewed (all nodes on one branch). The problem guarantees a valid n-ary tree input, so we do not need to handle invalid structures.
Approaches
A brute-force approach would attempt a recursive traversal without separating levels. One might perform a pre-order or post-order traversal and then group values afterward by tracking depths with an auxiliary list. While this works in theory, it requires traversing the tree and then performing extra operations to group by depth, which is less efficient in both time and space.
The optimal approach is to use Breadth-First Search (BFS). BFS naturally traverses the tree level by level. By using a queue, we can dequeue nodes of a given level, record their values, and enqueue their children for the next level. This ensures that each level is handled separately and efficiently, avoiding the need for additional grouping steps.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(N) | O(N) | Traverse all nodes, then group by depth using extra memory. |
| Optimal | O(N) | O(N) | BFS directly processes nodes level by level using a queue. |
Algorithm Walkthrough
-
Initialize an empty list
resultto store the final output. This will hold sublists for each level of the tree. -
If the root is
None, return the emptyresultimmediately as the tree is empty. -
Initialize a queue and enqueue the root node. This queue will help us process nodes level by level.
-
While the queue is not empty, repeat the following steps:
-
Determine the number of nodes at the current level by measuring the queue size.
-
Initialize an empty list
current_levelto store values of nodes at this level. -
Dequeue nodes one by one according to the level size. For each node, append its value to
current_level. -
Enqueue all children of the current node into the queue to process them in the next iteration.
-
After processing all nodes of the current level, append
current_leveltoresult. -
After the queue is empty, return
result.
Why it works: BFS guarantees that nodes are visited level by level. By keeping track of the current queue size, we isolate nodes at each depth. The invariant is that the queue always contains all nodes for the next level before we start processing them, ensuring the output list correctly represents the tree level order traversal.
Python Solution
from typing import List, Optional
"""
# Definition for a Node.
class Node:
def __init__(self, val: Optional[int] = None, children: Optional[List['Node']] = None):
self.val = val
self.children = children
"""
class Solution:
def levelOrder(self, root: 'Node') -> List[List[int]]:
result: List[List[int]] = []
if not root:
return result
from collections import deque
queue = deque([root])
while queue:
level_size = len(queue)
current_level: List[int] = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.children:
queue.extend(node.children)
result.append(current_level)
return result
Explanation: We initialize the result list and a queue containing the root. For each level, we measure the number of nodes to process. We iterate through the nodes at that level, record their values, and enqueue their children for the next level. The BFS ensures we process each level sequentially and capture all node values correctly.
Go Solution
/**
* Definition for a Node.
* type Node struct {
* Val int
* Children []*Node
* }
*/
func levelOrder(root *Node) [][]int {
var result [][]int
if root == nil {
return result
}
queue := []*Node{root}
for len(queue) > 0 {
levelSize := len(queue)
var currentLevel []int
for i := 0; i < levelSize; i++ {
node := queue[0]
queue = queue[1:]
currentLevel = append(currentLevel, node.Val)
if node.Children != nil {
queue = append(queue, node.Children...)
}
}
result = append(result, currentLevel)
}
return result
}
Explanation: Go requires careful handling of slices. We initialize a queue with the root. For each level, we track its size, remove nodes from the front of the slice, append their values, and enqueue children using append. Nil checks prevent panics when a node has no children.
Worked Examples
Example 1: Input [1,null,3,2,4,null,5,6]
| Step | Queue State | Current Level | Result |
|---|---|---|---|
| Initial | [1] | [] | [] |
| Level 0 | [] | [1] | [[1]] |
| Level 1 | [3,2,4] | [3,2,4] | [[1],[3,2,4]] |
| Level 2 | [5,6] | [5,6] | [[1],[3,2,4],[5,6]] |
Example 2: Input [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
| Step | Queue State | Current Level | Result |
|---|---|---|---|
| Level 0 | [1] | [1] | [[1]] |
| Level 1 | [2,3,4,5] | [2,3,4,5] | [[1],[2,3,4,5]] |
| Level 2 | [6,7,8,9,10] | [6,7,8,9,10] | [[1],[2,3,4,5],[6,7,8,9,10]] |
| Level 3 | [11,12,13] | [11,12,13] | [[1],[2,3,4,5],[6,7,8,9,10],[11,12,13]] |
| Level 4 | [14] | [14] | [[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]] |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N) | Each node is visited exactly once. |
| Space | O(N) | Queue can hold at most all nodes at one level in the worst case. |
The complexity is linear in the number of nodes because BFS touches each node once, and the queue grows proportional to the maximum width of the tree, which can be up to N in the worst case.
Test Cases
# Provided examples
assert Solution().levelOrder(None) == [] # Empty tree
assert Solution().levelOrder(Node(1, [Node(3, [Node(5), Node(6)]), Node(2), Node(4)])) == [[1],[3,2,4],[5,6]]
assert Solution().levelOrder(Node(1, [Node(2), Node(3), Node(4), Node(5, [Node(6), Node(7), Node(8, [Node(9), Node(10, [Node(11), Node(12), Node(13, [Node(14)])])])])])) == [[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
# Single node tree
assert Solution().levelOrder(Node(42)) == [[42]]
# Skewed tree
assert Solution().levelOrder(Node(1, [Node(2, [Node(3, [Node(4)])])])) == [[1],[2],[3],[4]]
# Nodes with empty children lists
assert Solution().levelOrder(Node(1, [Node(2, []), Node(3, []), Node(4, [])])) == [[1],[2,3,4]]
| Test | Why |
|---|---|
| Empty tree | Validates handling of None input |
| Example 1 | Standard multi-level tree |
| Example 2 | Complex tree with multiple levels |
| Single node | Checks minimal tree case |
| Skewed tree | Ens |