662 · Max Width — gaps count too

662 · Maximum Width of Binary Tree

Width counts the null nodes too. Two real nodes sitting far apart on the same level — with nothing but missing children between them — still define a wide level, as if the tree were laid out as a complete binary tree with every slot present.


1 — The problem

The width of one level is the distance between its leftmost and rightmost non-null node, measured as if the whole tree were a complete binary tree — i.e. by pretending every node has an index, root at 1 (or 0), left child at 2×i, right child at 2×i + 1, whether or not that child actually exists. The answer is the largest such width over all levels.

This is why the problem is harder than it first looks: a level can be "wide" purely because two real nodes happen to be far-flung leaves several missing generations apart, with nothing but absent children in between. The visualiser's second row below makes that gap literal — it draws the missing slots as dashed, discarded cells sitting right there in the row.

The naive version of this index scheme doubles the index at every level down, which overflows a 32-bit (or even 64-bit) integer for a deep, lopsided tree in a hurry. The fix costs nothing: re-anchor every level to its own first index — subtract the leftmost index of the level from every index in it before computing children. Widths are differences, so they're completely unaffected by where you put the zero point; only the raw magnitude of the numbers changes.


2 — Visualizing the index gaps

BFS with normalised positions[1,3,2,5,null,null,9,6,null,7]interactive
Queue<int[]> q = new LinkedList<>(); // {node placeholder, index}q.add(new Object[]{root, 0L});long maxWidth = 0;while (!q.isEmpty()) {    int sz = q.size();    long first = peekIndex(q, 0), last = peekIndex(q, sz - 1);    maxWidth = Math.max(maxWidth, last - first + 1);    for (int i = 0; i < sz; i++) {        var entry = q.poll();        long idx = entry.index - first; // re-anchor to this level        if (entry.node.left  != null) q.add(entry(entry.node.left,  2 * idx));        if (entry.node.right != null) q.add(entry(entry.node.right, 2 * idx + 1));    }}

The bottom row is the level laid out as a complete binary tree would be: real nodes in their true slot, everything else a discarded gap. The last level shown is the one that matters for this example — two real nodes, five missing slots between them, width 7. That's the entire answer to the problem, and it comes from a level with only two actual tree nodes in it.


3 — Complexity and edge cases

  • Time: O(n) — every real node is visited once.
  • Space: O(w) for the queue, where w is the widest level by node count (not by index span — those can differ enormously, which is the whole point of the problem).
  • Overflow: without re-anchoring, a skewed tree of depth d can produce indices near 2d; re-anchoring every level keeps the live indices bounded by the level's own node count times a small constant, regardless of overall depth.
  • A single node: width 1.
  • A perfectly balanced tree: width doubles every level, so the answer is simply the node count of the bottom level — the null-gap subtlety never triggers because there are no gaps.
  • The interesting case is exactly the one visualised: a level whose real nodes are separated by long stretches of missing descendants elsewhere in the tree.

4 — Reference implementation

Java 21Record-based queue entries, re-anchored per level.21 lines
public int widthOfBinaryTree(TreeNode root) {
    record Entry(TreeNode node, long index) {}
    if (root == null) return 0;
    Queue<Entry> q = new LinkedList<>();
    q.add(new Entry(root, 0L));
    long maxWidth = 0;
    while (!q.isEmpty()) {
        int sz = q.size();
        long first = q.peek().index();
        long last  = first;
        for (int i = 0; i < sz; i++) {
            Entry e = q.poll();
            long idx = e.index() - first; // re-anchor
            last = e.index();
            if (e.node().left  != null) q.add(new Entry(e.node().left,  2 * idx));
            if (e.node().right != null) q.add(new Entry(e.node().right, 2 * idx + 1));
        }
        maxWidth = Math.max(maxWidth, last - first + 1);
    }
    return (int) maxWidth;
}