Maximum Width of Binary Tree
MedPrompt
Given the root of a binary tree, return the maximum width of the given tree.
The maximum width of a tree is the maximum width among all levels.
The width of one level is defined as the length between the end-nodes (the leftmost and rightmost non-null nodes), where the null nodes between the end-nodes that would be present in a complete binary tree extending down to that level are also counted into the length calculation.
It is guaranteed that the answer will in the range of a 32-bit signed integer.
Example 1:
Input: root = [1,3,2,5,3,null,9]
Output: 4
Explanation: The maximum width exists in the third level with length 4 (5,3,null,9).Example 2:
Input: root = [1,3,2,5,null,null,9,6,null,7]
Output: 7
Explanation: The maximum width exists in the fourth level with length 7 (6,null,null,null,null,null,7).Example 3:
Input: root = [1,3,2,5]
Output: 2
Explanation: The maximum width exists in the second level with length 2 (3,2).
Constraints:
- The number of nodes in the tree is in the range
[1, 3000]. -100 <= Node.val <= 100
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a Breadth-First Search (BFS) to traverse the tree level by level. The core idea is to assign a numerical index to each node as if it were in a complete binary tree. The width of a level is then the difference between the indices of the rightmost and leftmost nodes, plus one.
Algorithm
- Create a queue to store pairs of
(TreeNode, Integer)representing the node and its calculated index. - If the root is null, return 0.
- Add the root node with an initial index of 0 to the queue.
- Initialize a variable
maxWidthto 0. - Loop while the queue is not empty, processing one level at a time:
- Get the number of nodes on the current level (
levelSize). - Peek at the first node in the queue to get the starting index for this level (
startIdx). This is used for re-basing indices to prevent overflow. - Initialize
leftmostIdxandrightmostIdxfor the current level. - Loop
levelSizetimes to process all nodes on the level:- Dequeue a
(node, index)pair. - If it's the first node of the level, store its index in
leftmostIdx. - If it's the last node of the level, store its index in
rightmostIdx. - Calculate a
relativeIndex = index - startIdx. - If the node has a left child, enqueue it with a new index of
2 * relativeIndex + 1. - If the node has a right child, enqueue it with a new index of
2 * relativeIndex + 2.
- Dequeue a
- After the level is processed, calculate its width as
rightmostIdx - leftmostIdx + 1. - Update
maxWidthwith the maximum width found so far.
- Get the number of nodes on the current level (
- Return
maxWidth.
Walkthrough
We use a queue to perform the level-order traversal. Instead of just storing the tree nodes, we store pairs of (TreeNode, index). The root node is assigned index 0. For any node at index i, its left child would be at 2*i + 1 and its right child at 2*i + 2 in a complete binary tree.
A potential issue is that these indices can grow very large and cause an integer overflow, especially in deep trees. To prevent this, we re-normalize the indices at the beginning of each level. The index of the first node at each level is used as an offset. For any node with an original index p_idx at a level where the leftmost node has index min_idx, its new relative index becomes p_idx - min_idx. The children's indices are then calculated based on this new relative index, which keeps the index values small and manageable.
// Helper class to store node and its indexclass NodeInfo { TreeNode node; int index; NodeInfo(TreeNode node, int index) { this.node = node; this.index = index; }} class Solution { public int widthOfBinaryTree(TreeNode root) { if (root == null) { return 0; } Queue<NodeInfo> queue = new LinkedList<>(); queue.offer(new NodeInfo(root, 0)); int maxWidth = 0; while (!queue.isEmpty()) { int levelSize = queue.size(); NodeInfo head = queue.peek(); int startIdx = head.index; int leftmostIdx = 0, rightmostIdx = 0; for (int i = 0; i < levelSize; i++) { NodeInfo current = queue.poll(); TreeNode node = current.node; int index = current.index; if (i == 0) { leftmostIdx = index; } if (i == levelSize - 1) { rightmostIdx = index; } // Re-base index to prevent overflow int relativeIndex = index - startIdx; if (node.left != null) { // Use long for intermediate calculation to prevent overflow queue.offer(new NodeInfo(node.left, (int)(2L * relativeIndex + 1))); } if (node.right != null) { queue.offer(new NodeInfo(node.right, (int)(2L * relativeIndex + 2))); } } maxWidth = Math.max(maxWidth, rightmostIdx - leftmostIdx + 1); } return maxWidth; }}Complexity
Time
O(N), where N is the number of nodes in the tree. Each node is enqueued and dequeued exactly once.
Space
O(W), where W is the maximum number of nodes at any single level. In the worst-case scenario of a complete binary tree, the last level can contain up to `(N+1)/2` nodes, making the space complexity O(N).
Trade-offs
Pros
Intuitive and straightforward for a level-based problem.
Guarantees finding the maximum width because it systematically checks every level.
Generally more space-efficient than DFS for very deep and narrow trees.
Cons
Can be less space-efficient than DFS for tall, skinny trees, as the queue can grow to the width of the widest level.
The logic for re-basing indices at each level adds a bit of complexity compared to a standard BFS.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public int widthOfBinaryTree ( TreeNode root ) { Deque < Pair < TreeNode , Integer >> q = new ArrayDeque <>(); q . offer ( new Pair <>( root , 1 )); int ans = 0 ; while (! q . isEmpty ()) { ans = Math . max ( ans , q . peekLast (). getValue () - q . peekFirst (). getValue () + 1 ); for ( int n = q . size (); n > 0 ; -- n ) { var p = q . pollFirst (); root = p . getKey (); int i = p . getValue (); if ( root . left != null ) { q . offer ( new Pair <>( root . left , i << 1 )); } if ( root . right != null ) { q . offer ( new Pair <>( root . right , i << 1 | 1 )); } } } return ans ; } }Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.