Topics › Binary Trees › Traversals
Level Order Traversal
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given the root of a binary tree, return its values level by level: first the root, then all nodes at depth 1 from left to right, then all nodes at depth 2 from left to right, and so on. The answer is a list of lists, one inner list per level. An empty tree gives an empty outer list.
Constraints: between 0 and 10^5 nodes (LeetCode uses at most 2000), arbitrary 32-bit values. The tree can be very unbalanced, even a single chain.
Trees are written in LeetCode’s level-order form, where null marks a missing child.
Example 1
3
/ \
9 20
/ \
15 7
root = [3, 9, 20, null, null, 15, 7]
answer = [[3], [9, 20], [15, 7]]
Example 2
1
/ \
2 3
\ \
4 5
root = [1, 2, 3, null, 4, null, 5]
answer = [[1], [2, 3], [4, 5]] // gaps inside a level are skipped; 4 and 5 are still neighbours
Preorder, inorder and postorder are depth-first: they finish one branch before starting the next. Level order is breadth-first: it finishes one depth before starting the next. That makes it the natural tool for anything phrased in terms of levels: the view from one side, the width at each level, the minimum depth, zigzag order, and connecting nodes to their right neighbour.
Approach 1: Brute force, one pass per level
Intuition. The most literal reading is: for depth 0, list all nodes at depth 0; for depth 1, list all nodes at depth 1; and so on, down to the last level. A recursive helper can collect the nodes at a given depth d by walking down and decrementing d; when it reaches 0, the current node is at the wanted depth. Visiting left before right keeps each level in left-to-right order. We first compute the height so we know how many levels there are.
Algorithm.
- Compute
h, the number of levels. - For each
dfrom0toh - 1: walk the tree from the root, appending every node reached withd == 0, and add that list to the answer.
static List<List<Integer>> bruteForce(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
int h = height(root);
for (int d = 0; d < h; d++) {
List<Integer> level = new ArrayList<>();
collect(root, d, level);
levels.add(level);
}
return levels;
}
private static void collect(TreeNode node, int d, List<Integer> level) {
if (node == null) return;
if (d == 0) { // this node is exactly at the wanted depth
level.add(node.val);
return;
}
collect(node.left, d - 1, level); // left first keeps the level in left-to-right order
collect(node.right, d - 1, level);
}
private static int height(TreeNode node) {
return node == null ? 0 : 1 + Math.max(height(node.left), height(node.right));
}
Complexity. Time O(n * h) in the worst case: the pass for depth d touches every node at depth d or less. On a perfect tree each level is twice the size of the one above, so the passes add up to only about 2n visits, but on a chain (h = n) they cost 1 + 2 + ... + n, which is O(n^2). Space O(h) for the recursion. Every level is recomputed from scratch, which is the waste the next approaches remove.
Approach 2: One DFS that carries the depth
Intuition. We do not need one pass per level. A single depth-first walk can carry the current depth along and append each node to the list belonging to its depth. When the walk reaches a depth for the first time, that list does not exist yet, and because depths are discovered in increasing order, the new list always goes at the end.
Why are the levels still in left-to-right order even though the walk jumps between depths? Take two nodes on the same level, a to the left of b. Their lowest common ancestor has a in its left subtree and b in its right subtree, and a preorder walk finishes the entire left subtree before entering the right one. So a is appended before b.
Algorithm.
- Start with an empty list of levels and call
dfs(root, 0). - In
dfs(node, depth): ifnodeisnull, return. Ifdepth == levels.size(), append a new empty list. Appendnode.valtolevels[depth]. Recurse left, then right, withdepth + 1.
static List<List<Integer>> dfsWithDepth(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
dfs(root, 0, levels);
return levels;
}
private static void dfs(TreeNode node, int depth, List<List<Integer>> levels) {
if (node == null) return;
if (depth == levels.size()) levels.add(new ArrayList<>()); // first node ever seen at this depth
levels.get(depth).add(node.val);
dfs(node.left, depth + 1, levels); // left before right keeps each level ordered
dfs(node.right, depth + 1, levels);
}
Complexity. Time O(n): one visit per node, and every list operation is amortised O(1). Space O(h) for the recursion. That is linear, but it still relies on the call stack, so a chain of 10^5 nodes can overflow it.
Approach 3: Optimal, breadth-first search with a queue
Intuition. A queue is first-in-first-out, which is exactly the order of levels: if we put the root in, then repeatedly take a node out and put its children in, nodes come out level by level, left to right. The only extra question is where one level ends and the next begins. The answer comes from an invariant: at the start of each round, the queue contains exactly one complete level. So we read queue.size() once, take out precisely that many nodes as the current level, and everything they add to the back of the queue forms the next level.
Algorithm.
- If the root is
null, return an empty list. Otherwise enqueue the root. - While the queue is not empty:
- Let
sizebe the current queue size. - Dequeue
sizenodes, appending each value to a new list and enqueueing each node’s existing children. - Add the list to the answer.
- Let
static List<List<Integer>> bfs(TreeNode root) {
List<List<Integer>> levels = new ArrayList<>();
if (root == null) return levels;
Queue<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size(); // exactly the nodes of the current level
List<Integer> level = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left); // children join the back of the queue,
if (node.right != null) queue.offer(node.right); // behind the rest of this level
}
levels.add(level);
}
return levels;
}
Dry run on Example 1.
| Round | Queue at start | size | Level produced | Queue at end |
|---|---|---|---|---|
| 1 | [3] | 1 | [3] | [9, 20] |
| 2 | [9, 20] | 2 | [9, 20] | [15, 7] |
| 3 | [15, 7] | 2 | [15, 7] | [] |
Why it works. The invariant holds at the start (the queue holds the root, which is level 0). If the queue holds exactly level d in left-to-right order, then dequeuing those nodes and enqueuing their children in left-then-right order puts exactly level d + 1 into the queue, again left to right, because the children of a left node come before the children of a node further right.
Complexity. Time O(n): each node is enqueued and dequeued once. Space O(w), where w is the maximum width of the tree: the queue holds at most one level plus part of the next. For a perfect tree the last level has about n / 2 nodes, so the worst case is O(n); for a chain it is O(1). There is no recursion, so the depth of the tree does not matter. The Java tests run BFS on a chain of 10^5 nodes.
Edge cases and pitfalls
- Empty tree. The answer is
[], not[[]]. Return before enqueuing, sinceArrayDequerejectsnull. - Freeze the level size. Writing
for (int i = 0; i < queue.size(); i++)is a classic bug: the size changes as children are added, so levels bleed into each other. Read the size once before the loop. - Skip missing children. Enqueue a child only when it exists. Gaps inside a level, as in Example 2, then disappear naturally.
- Single node. The answer is
[[x]]. - Skewed trees. Each level has one node. The brute force becomes quadratic and the DFS becomes deep; BFS is unaffected.
- Queue methods.
offerandpollreturn a special value on failure instead of throwing; with an unboundedArrayDequeboth families behave the same here.
Related problems
- Zig Zag or Spiral Traversal reverses every other level of this traversal.
- Right/Left View of Binary Tree keeps only the last or first node of each level.
- Maximum Width of BT measures each level using position indices.
- Maximum Depth in BT is the number of levels BFS produces.
- Vertical Order Traversal runs BFS while tracking a column for each node.
- Serialize and De-serialize BT can write a tree in level order with markers for missing children.
- Preorder Traversal is the depth-first counterpart.