Problem Overview — Find the Maximum Width Across All Levels
LeetCode 662 Maximum Width of Binary Tree asks you to find the maximum width of a binary tree. The width of a level is defined as the number of nodes between the leftmost and rightmost non-null nodes at that level, including any null nodes that fall between them. For example, if a level has nodes at positions 1 and 5 with nulls at 2, 3, and 4 between them, the width is 5 (positions 1 through 5 inclusive).
This definition of width is what makes the problem non-trivial. A simple BFS that counts non-null nodes per level will undercount because it ignores the null gaps between non-null nodes. The key insight is that width depends on the positional spread of nodes across a level, not just how many nodes exist at that level.
The problem guarantees the number of nodes is between 1 and 3000, and node values are between -100 and 100. The answer is guaranteed to fit in a 32-bit signed integer, though intermediate index values can grow very large for deep trees and require careful overflow handling.
- The number of nodes is in the range [1, 3000]
- Node values are in the range [-100, 100]
- Width = number of positions from leftmost to rightmost non-null node inclusive
- Null nodes between non-null nodes count toward the width
- Return the maximum width across all levels of the tree
- Answer is guaranteed to fit in a 32-bit integer
Why Simple BFS Is Not Enough — Null Gaps Change the Width
A naive BFS implementation processes nodes level by level and counts how many non-null nodes exist at each level. This approach correctly handles trees where all levels are complete, but it fails as soon as any node is missing in the interior of a level. The width is not the count of non-null nodes — it is the span from the leftmost to the rightmost occupied position.
Consider a tree where the root has only a right child, and that right child has only a right child. At depth 2, there is only one non-null node at the rightmost position. A naive BFS would report width 1. The correct answer is 3, because the rightmost position at depth 2 is index 2 (in 0-indexed heap notation), and the leftmost is index 2 — but at depth 1 the right child is at index 2, and its right child is at index 2*2+2 = 6 at depth 2. The leftmost occupied index is also 6, giving width 1, but intermediate levels show the span effect clearly across different examples.
The correct approach is to assign a positional index to every node — whether present or absent — using the same numbering scheme as a heap array. Once every node carries its positional index, the width at any level is simply the rightmost index minus the leftmost index plus one, regardless of how many null gaps lie between them.
Heap-Style Index Assignment — Root=0, Left Child=2i+1, Right Child=2i+2
Assign positional indices to every node using heap array numbering: the root gets index 0, the left child of a node at index i gets index 2*i+1, and the right child gets index 2*i+2. This numbering preserves positional relationships across levels — nodes at the same depth that would be adjacent in a complete tree get consecutive indices. The width at any level is then simply: rightmost_index - leftmost_index + 1, which correctly counts all positions including null gaps between the two endpoints.
BFS with Index Tracking — Queue Stores Node and Index Pairs
The BFS solution extends the standard level-order traversal by storing each node together with its positional index in the queue. Initialize the queue with the root node at index 0. At the start of each level, record the index of the first node in the queue. Process all nodes at the current level: for each node, enqueue its left child at index 2*i+1 and its right child at index 2*i+2 when they exist. After processing the entire level, compute the width as the last index seen minus the first index recorded plus one.
Track the maximum width across all levels and return it after BFS completes. The time complexity is O(n) because every node is enqueued and dequeued exactly once. The space complexity is O(w) where w is the maximum width of the tree, since the queue holds at most one full level of nodes at a time. In the worst case — a complete binary tree — the last level contains n/2 nodes, giving O(n) space.
The implementation is straightforward with Python's collections.deque or Java's ArrayDeque. The key bookkeeping is capturing the first index at the start of each level and the last index at the end of each level. A clean way to implement this is to process each level in a bounded loop using the current queue size, which naturally separates levels without needing sentinel values.
- 1Initialize queue with (root, 0) — root node at index 0
- 2Set maxWidth = 0
- 3While queue is not empty: record levelSize = len(queue)
- 4Record firstIndex = index of first element in queue for this level
- 5Process levelSize nodes: dequeue (node, idx), enqueue (node.left, 2*idx+1) and (node.right, 2*idx+2) if they exist; track lastIndex
- 6After each level: maxWidth = max(maxWidth, lastIndex - firstIndex + 1)
- 7Return maxWidth after BFS completes
Index Overflow Prevention — Normalize Indices at Each Level
For a balanced tree of depth d, the maximum index at depth d is 2^(d+1) - 2. For a tree with depth 60, this exceeds 2^61, which overflows a 64-bit signed integer. Even in Python, where integers have arbitrary precision, carrying enormous indices through the entire traversal is wasteful. In Java and C++, overflow silently corrupts the width calculation and produces wrong answers.
The fix is index normalization: at the start of each level, subtract the leftmost index of that level from every index at that level. This shifts all indices so the leftmost node at each level always has index 0. The width calculation remains correct because width = rightmost - leftmost = (rightmost - offset) - (leftmost - offset) + 1 = normalized_rightmost - 0 + 1 = normalized_rightmost + 1. Normalization keeps indices bounded by the width of the level rather than the absolute position in a full tree.
In practice, normalization is done when enqueuing children: when a node at normalized index i enqueues its left child, assign the left child index 2*i+1 relative to the already-normalized parent index. Since each level resets, the indices never grow beyond the level width, which is bounded by n (the total number of nodes). This keeps all index values within safe 32-bit range for any input within the problem constraints.
Normalize Indices Per Level to Prevent Exponential Growth
Without normalization, indices grow as 2^depth. For a tree with depth 32, right-skewed indices reach 2^32 which overflows a 32-bit integer. For depth > 60, they overflow 64-bit integers. The fix: at the start of each level, subtract the leftmost index from all indices at that level. This resets the leftmost node to index 0 each level. The width calculation is unaffected because width depends only on the difference between rightmost and leftmost — subtracting a constant from both leaves the difference unchanged. Python handles big integers natively but still benefits from normalization for performance.
DFS Alternative — Track First Index Seen at Each Depth
A DFS approach solves the same problem by performing a pre-order traversal while tracking the current depth and current positional index. Maintain a dictionary or array that records the first index seen at each depth level. When visiting a node at depth d with index i, if no first index has been recorded for depth d yet, store i as the first index for that depth. Then update the maximum width as max(maxWidth, i - firstIndex[d] + 1).
Recurse into the left child with index 2*i+1 and depth d+1, then into the right child with index 2*i+2 and depth d+1. Pre-order traversal ensures the leftmost node at each depth is visited before the rightmost, so the first index stored per depth is always the leftmost index at that depth — exactly what is needed for the width calculation.
The DFS approach uses O(h) space where h is the tree height (call stack depth), versus O(w) for BFS. For a balanced tree, O(h) = O(log n) which is better than BFS. For a skewed tree, O(h) = O(n) which matches BFS worst case. Both approaches have O(n) time complexity. The DFS approach is slightly more compact in code, though the BFS approach is more intuitive for level-oriented problems.
- 1Initialize firstIndex = {} (empty map from depth to first index seen)
- 2Define dfs(node, depth, index): if node is None, return
- 3If depth not in firstIndex: firstIndex[depth] = index (first visit at this depth)
- 4Update maxWidth = max(maxWidth, index - firstIndex[depth] + 1)
- 5Recurse: dfs(node.left, depth+1, 2*index+1)
- 6Recurse: dfs(node.right, depth+1, 2*index+2)
- 7Call dfs(root, 0, 0) and return maxWidth
Code Walkthrough — Python and Java BFS Implementations
Python BFS implementation uses collections.deque storing (node, index) tuples. At the start of each level iteration, capture the queue length and the first index. Loop that many times: dequeue a node and its index, enqueue children with 2*idx+1 and 2*idx+2 while subtracting the first index of the current level (normalization). After each level, update maxWidth with lastIndex - firstIndex + 1. The complete solution is about 15 lines. Time O(n), space O(w) where w is the maximum width.
Java BFS implementation uses ArrayDeque of int[] arrays where each array holds {node reference as id, index}. Because Java does not allow storing object references alongside primitives directly, a common approach is to use a Queue of a simple Pair class or an int[] with the TreeNode stored separately in a parallel structure. Alternatively, use a Queue<long[]> where the long stores a packed representation — though for this problem a simple Queue<Object[]> storing {TreeNode, Long} suffices. Normalize indices per level to avoid overflow.
Both Python and Java solutions follow the same structure: BFS with (node, index) pairs, level-by-level processing, per-level normalization, and max tracking. The core logic is identical; the difference is syntax and data structure idioms. Index normalization is critical in Java to avoid integer overflow — without it, indices exceed Integer.MAX_VALUE for trees with depth beyond 30.
Java and C++ Will Overflow Without Index Normalization
Python handles arbitrarily large integers natively, so it will produce correct answers even without normalization (just slowly for very deep trees). Java and C++ use fixed-width integers — without normalization, indices overflow at depth ~30 for int and depth ~62 for long, silently producing wrong answers with no error. Always normalize indices by subtracting the leftmost index at each level before enqueuing children. This is a one-line fix that keeps all indices within the width of the current level, bounded well below Integer.MAX_VALUE for any valid input.