LC 662 Maximum Width of Binary Tree
Interview pattern Β· Tree geometry

Maximum Width of Binary Tree

The width of a level isn't how many nodes sit on it. It's how much hallway stretches between the outermost two β€” empty rooms included. This lesson installs that intuition permanently.

LeetCode 662 Difficulty Medium Time O(n) Core trick index normalization Asked at Google Β· Amazon Β· Meta
01

The Problem in Plain English

Imagine your binary tree is actually a seating chart in a theater.

Row 0 has 1 seat. Row 1 has 2 seats. Row 2 has 4 seats. Row 3 has 8 seats. Every row doubles. This is true whether or not anyone is sitting there β€” the seats exist regardless.

Now some people show up and sit down. Not everybody. The chart is patchy β€” a person here, three empty seats, a person there.

For each row, I want you to do exactly one thing:

β–Έ The whole task

Point at the leftmost occupied seat. Point at the rightmost occupied seat. Now count every seat from your left finger to your right finger β€” including all the empty ones in between.

That number is the row's width. Do that for every row. Tell me the biggest number you got. Done. That's the whole problem.

The important, sneaky thing: empty seats in the middle still count. If row 5 has one person at the far left and one person at the far right and 30 empty seats between them, that row's width is 32, not 2.

But empty seats outside the two people don't count. We measure from person to person, not wall to wall.

That's it. No algorithm words. Just: how far apart are the two outermost people on the most spread-out row?

row 0 [ 1 ] width 1 row 1 [ 3 ][ 2 ] width 2 row 2 [ 5 ][ Β· ][ Β· ][ 9 ] width 4 ^^^^^^^^^^ these two count row 3 [ 6 ][ Β· ][ Β· ][ Β· ][ Β· ][ Β· ][ Β· ][ 7 ] width 8 ^^^^^^^^^^^^^^^^^^^^^^^^^^ all six count
02

Why Most People Get Stuck

Here's the trap, and almost everyone falls into it.

You read "maximum width," you think "level order traversal," you feel great, you write BFS, and then you write this:

And it feels so right. Queue size = number of nodes on this level = width, right?

No. Queue size counts people. The problem asks about seats. Those are completely different numbers the moment the tree has holes in it.

1 / \ 3 2 / \ 5 9 bottom row: 2 nodes β†’ queue.size() says width = 2 bottom row: 5 and 9 are separated by two ghost seats β†’ real width = 4
⚠️ The mental trap

You've been trained to think a tree is a set of pointers, so you only "see" the nodes that exist. This problem is asking you to see the nodes that don't exist.

Your brain has no data structure for those. They're not in memory. There's no null object sitting there with a position. They're just… absence.

And so people flail. They try:

  • Padding the tree with fake null nodes β†’ memory explodes on a skewed tree. A 1000-deep tree would need 21000 fake nodes.
  • Recording "gaps" with counters β†’ gets insanely fiddly and breaks on nested holes.
  • A boolean array per level β†’ same explosion problem.

Every one of these fails for the same reason: you're trying to materialize the empty space. And empty space, by definition, refuses to be materialized cheaply.

The unlock is realizing you don't have to. You just have to number it.

03

The Key Observation

πŸ’‘ Key Observation

Every node has a fixed seat number, determined entirely by the path taken to reach it β€” and that seat number exists whether or not the neighbours exist.

Let me convince you, not just tell you.

Take a node sitting at position i in its row (counting from 0, left to right, across the full theoretical row including empty seats).

Ask: where do its children sit?

Each of the i seats to its left β€” occupied or not β€” has room for exactly 2 children in the next row. So before this node's children begin, there are exactly 2i seats already spoken for. Therefore:

i parent seat 2i left child 2i+1 right child
β†’ children at 94 and 95

That's not a convention someone invented. That's arithmetic. The row below is literally twice as wide, so positions double.

Why this destroys the problem

Seat numbers are computed from the parent alone. They don't need to know about siblings, cousins, gaps, or anything else. A node at position 47 produces children at 94 and 95 β€” full stop. It doesn't matter if positions 0 through 93 are a barren wasteland.

The empty seats get numbered for free. You never build them, never store them, never visit them. They exist purely as the integers you skipped.

And then the finish:

πŸ’‘ The formula that does all the work

width = rightmostSeat βˆ’ leftmostSeat + 1

Subtraction counts the gap. Gaps are exactly what the problem asked for. You have converted "count the invisible things" into "subtract two visible numbers."

That's the whole trick. Everything else is bookkeeping.

Click a node to see the rule with your own eyes

Every circle below is a seat in the perfect theatre. Click one β€” its two children light up, and the arithmetic appears underneath.

Click any seat.

The second observation β€” the one that separates a pass from a fail

Seat numbers double every level. Row 40 has seat numbers around 240. Row 60 has seat numbers around 260.

Your int dies at 231. Your long dies at 263. A tree can easily be 100 levels deep.

So a right-leaning tree β€” root, right, right, right, sixty times β€” produces a node with seat number 260 βˆ’ 1, your int wraps to a negative number, and your answer becomes garbage.

But notice: on that right-leaning tree, every single row has exactly one node. Every row's width is 1. The answer is tiny. Only the labels are astronomical.

Which tells you the fix immediately:

πŸ’‘ Second observation

The absolute seat numbers were never the point. Only the differences were.

So at the start of each row, take the leftmost node's seat number and declare it zero. Shift everything on that row down by the same amount. Differences are unchanged β€” that's what subtracting a constant means β€” but the numbers stay small forever.

This is index normalization, and it is the entire reason this problem is rated Medium-hard instead of Easy.

Watch the overflow happen β€” and watch it get fixed

This is a right-leaning spine: root, right, right, right… Every level has exactly one node, so the true answer is always 1. Toggle normalization off and watch a 32-bit integer die at depth 31.

int arithmetic, 32-bit
04

The Movie

Welcome to the Hotel Fibonacci β€” sorry, Hotel Binaria. 🏨

An infinite hotel. Floor 0 has 1 room. Floor 1 has 2 rooms. Floor 2 has 4 rooms. Every floor down, rooms double. (Yes, the hotel is an upside-down pyramid. The architect was fired. He now works at Google.)

The house rule: when a guest in room i has children, hotel policy assigns them to rooms 2i and 2i+1 on the floor below. Always. It's in the contract. Nobody gets to pick.

The hotel is mostly empty. Guests are scattered. Rooms sit dark and dusty between them.

✦ HOTEL BINARIA ✦
every floor down, the rooms double
🧹

Meet Gary, the world's laziest carpet inspector. Press the button and watch him work.

Enter our hero: Gary, the world's laziest carpet inspector.

Gary's job: for each floor, measure how much carpet needs cleaning. Management's rule is brutally simple β€” "Clean from the first occupied room to the last occupied room. Everything between them gets cleaned too, even the empty rooms, because guests walk through that hallway."

Gary doesn't clean before the first guest. Gary doesn't clean after the last guest. Gary cleans first guest β†’ last guest, inclusive.

So Gary walks onto a floor, writes down the room number of the first guest, strolls to the far end, writes down the room number of the last guest, subtracts, adds one, and goes back to his nap. He never counts the empty rooms. He never even looks inside them. Subtraction did the counting.

Now the twist 🎬

Gary gets sent to floor 900. He arrives, pulls out his clipboard, and the room number is:

10633823966279326983230456482242756608

Gary's clipboard is an int. Gary's clipboard catches fire. πŸ”₯

So the hotel manager introduces local floor numbering. On every floor, a bellhop stands at the first occupied room and slaps a sticker on the door that says "ROOM 0." Everything else on that floor gets renumbered relative to that sticker. The guest 5 rooms down is now "room 5," even if her global number has 40 digits.

Does this break the doubling rule? Not even slightly β€” the bellhop just applies 2i and 2i+1 to the local number when assigning children, and the next floor's bellhop re-stickers again. Distances between guests are perfectly preserved, because shifting everyone on a floor by the same amount doesn't change how far apart they are.

Gary's clipboard survives. Gary keeps his job. Gary continues to not care about empty rooms.

Cast list

Hotel floor
Tree level
Room number
Node index
Policy 2i, 2i+1
Child index formula
Empty dusty rooms
Nulls that still count toward width
Gary subtracting two numbers
right βˆ’ left + 1
The bellhop's "ROOM 0" sticker
Index normalization
Clipboard catching fire
Integer overflow
Gary never entering empty rooms
Never materializing nulls

Remember Gary. Gary is your friend. Gary is lazy and Gary is correct.

05

Build the Algorithm Naturally

Let's invent this from nothing. Start stupid.

Attempt 0 β€” "Just count nodes per level"

maxWidth = max over levels of (number of nodes at that level)

Dies immediately on [1,3,2,5,null,null,9]. Bottom level has 2 nodes but width 4. The gaps are invisible to us. ❌

Lesson learned: we need to see the gaps.

Attempt 1 β€” "Fine, let's MAKE the gaps real"

Build a complete tree. Every missing child becomes an actual null placeholder node that we push into the queue. Now the queue holds every seat, occupied or not, and queue.size() is honest.

This works. And then someone hands you a right-skewed tree of 1000 nodes, you try to allocate 21000 placeholders, and the heat death of the universe arrives before your program does. ❌

Lesson learned: the gaps must be known but never stored. We need a way to talk about a seat without instantiating it.

Attempt 2 β€” "Give every node an address"

A name for something absent β€” that's just a number. So: label every node with the seat it occupies in the perfect tree.

Root gets 0. Node at i gives children 2i and 2i+1. Now a gap isn't an object, it's a skipped integer. Nothing gets allocated. The absent seats are absent, but addressable.

width(level) = maxIndexOnLevel βˆ’ minIndexOnLevel + 1

Empty rooms counted, zero empty rooms built. βœ…

Attempt 3 β€” "How do I get min and max per level?"

Two options, and they're both worth knowing:

  • BFS: process level by level. The queue is naturally in left-to-right order, so the first node you pop on a level is the leftmost and the last one you pop is the rightmost. You don't even need a min/max scan β€” order gives it to you free.
  • DFS (preorder, left before right): the very first time you ever set foot on depth d, you must be at the leftmost node of that depth β€” because preorder always exhausts left before touching right. So record first[d] on first arrival, and every later node at depth d computes index βˆ’ first[d] + 1 against it.

Both are O(n). Both correct. (We compare them in section 13.)

Attempt 4 β€” "My numbers exploded"

Run either on a 60-deep tree. Indices hit 260. int overflows into negative territory. Your width becomes some cursed number like -1073741820. ❌

Stare at it. Ask: what do I actually use these indices for?

Only one thing: subtracting two of them within the same level. Never across levels. Never as an identity. Never as a key. Just subtraction, within a level.

And subtraction is invariant to shifting: (a βˆ’ c) βˆ’ (b βˆ’ c) = a βˆ’ b.

So: at the start of every level, subtract the leftmost index from everybody on that level. Leftmost becomes 0. Generate children from the shifted value. The next level re-shifts. Indices never grow beyond roughly the true width of the level. βœ…

The final algorithm β€” which now feels inevitable

  1. BFS with (node, index) pairs. Root is (root, 0).
  2. At each level: snapshot levelSize = queue.size(), and leftMost = queue.peek().index.
  3. Pop exactly levelSize nodes. Track the index of the last one popped as rightMost.
  4. For each popped node: normalize norm = index βˆ’ leftMost, push children at 2Β·norm and 2Β·norm + 1.
  5. maxWidth = max(maxWidth, rightMost βˆ’ leftMost + 1).
🎯 Interview Tip

Every single line traces back to a failure we personally suffered. That's what makes it stick β€” and it's also the story to tell out loud in an interview. Narrating "counting nodes fails β†’ padding nulls explodes β†’ so index them instead" makes you look like a problem solver rather than someone who memorised a solution.

06

Dry Run

Tree: [1, 3, 2, 5, null, null, 9, 6, null, null, 7]

1 ← level 0 / \ 3 2 ← level 1 / \ 5 9 ← level 2 / \ 6 7 ← level 3

Seats in the perfect theatre, so you can see the ghosts before we start:

level 0: [1] seat 0 level 1: [3][2] seats 0, 1 level 2: [5][Β·][Β·][9] seats 0, 1, 2, 3 level 3: [6][Β·][Β·][Β·][Β·][Β·][Β·][7] seats 0..7

Now the machine, one step at a time. Dashed circles are ghost seats β€” the algorithm never allocates them, but the subtraction counts them.

Step-by-step execution 1 / 17
levelSize–
leftMost–
rightMost–
norm–
maxWidth0
Queue
Answer: 8

Now watch normalization actually save your life

Different tree β€” a pure right spine:

1 \ 2 \ 3 \ ... (60 more)

Without normalization, indices go 0, 1, 3, 7, 15, 31, … 260 βˆ’ 1. At depth 31 the int wraps negative, and the moment any tree has a real spread at depth, you get garbage.

With normalization, each level has exactly one node, it's the leftmost, so norm = 0, and its child gets index 2·0 + 1 = 1. Next level: leftMost = 1, norm = 0, child gets 1 again. The index oscillates between 0 and 1 forever. Depth 5000? Still fine. Gary's clipboard survives. 🧯

07

Implementation Intuition

For each line, one question: what disaster happens if we delete it? Click to open each card.

if (root == null) return 0;
Disaster if removed

queue.offer(new Item(root, 0)) puts a null node in, then cur.node.left throws a NullPointerException on line one of the loop. Also, "width of an empty tree" is 0 by definition, not 1.

int levelSize = queue.size(); // BEFORE the inner loop
Disaster if removed β€” this is the #1 killer

If you instead write for (int i = 0; i < queue.size(); i++): you pop 1 node and push 2 children, so queue.size() is now bigger than when you started. The loop condition re-reads it, keeps going, and you start processing next level's nodes as if they were this level's. Levels smear into each other, rightMost picks up a child's index, and your width is nonsense.

levelSize is a snapshot. It's a fence between "now" and "next." Freeze it or lose it.

int leftMost = queue.peek().index;
Two jobs in one line

1. Measurement anchor. It's the left finger. Without it you have no baseline to subtract.

2. Overflow shield. It's the bellhop's "ROOM 0" sticker.

Remove it and indices double every level unchecked. Depth 31 β†’ int overflow β†’ negative indices β†’ right βˆ’ left + 1 produces absurd values. It doesn't crash. It just quietly lies. Those are the worst bugs.

Why peek() and not a min-scan? Because BFS enqueues left-to-right, level by level, and a queue is FIFO. The front of the queue at the start of a level is the leftmost node of that level. It's already sorted for you. Don't scan for a minimum β€” order gave it to you free.

int norm = cur.index - leftMost;
Disaster if removed

Children get generated from raw exploding indices, and normalization becomes purely cosmetic. You must normalize before generating children, not after. Shifting only the current level while feeding unshifted values downward means the explosion continues one level down. The shift has to propagate.

Why it's mathematically safe

Children of p are 2p + b. If we shift the parent by constant L, children become 2(p βˆ’ L) + b = 2p + b βˆ’ 2L. Every node on the child level gets shifted by the same constant 2L… plus whatever shift its own parent had. And since all parents on a level share the same L, all children share the same total shift. Uniform shift preserves all differences. Which is the only thing we ever measure. βœ…

rightMost = cur.index; // inside the loop
Disaster if you move it

If you instead try queue.peek().index after the loop, the queue now contains the next level. You'd measure the wrong row.

Why simple assignment (not Math.max) works: the last node popped in a level is, by FIFO order, the rightmost. Overwriting every iteration leaves you with the last one. Clean.

if (cur.node.left != null) { ... }
Disaster if removed

Null nodes enter the queue, levelSize counts them, cur.node.left throws an NPE, and β€” critically β€” a trailing null would corrupt rightMost, inflating your width past the last real node. Gary does not clean past the last guest.

This is also the line where you choose not to materialize the ghosts. It's the whole space-saving decision, hiding in an if.

2 * norm and 2 * norm + 1
Why exactly these numbers

Not arbitrary. 2Β·norm means "all norm seats before me each contributed 2 children, so mine start right after." The +1 means "right child sits immediately after left child." Swap them and your tree mirrors, breaking the leftmost-first guarantee that the whole method depends on.

rightMost - leftMost + 1
Disaster if the +1 goes missing

The +1 is inclusive counting. Seats 3 through 7 is 7 βˆ’ 3 + 1 = 5 seats, not 4. Forget it and every single answer is off by one β€” including the single-node tree, which would report 0.

08

Common Bugs

Twelve ways this goes wrong, in rough order of how often they actually happen.

The twelve classic failures 12 items
01 queue.size() in the loop condition

Level bleeding. The queue grows as you push children, so the loop keeps running into the next level. If your answer is too large and grows with tree size, look here first.

02 Missing the + 1

Every answer off by exactly one. Sanity check you can run in your head: a single-node tree must return 1.

03 Integer overflow from skipping normalization

Silent. Passes small tests, fails deep ones. Symptom: negative or wildly huge width. Note that long does not save you β€” it just moves the crash from depth 31 to depth 63. Normalization is the actual fix.

04 Normalizing the measurement but not the children

You compute norm for the width but push 2 * cur.index instead of 2 * norm. The width is right; the overflow is untouched. Classic half-fix.

05 Using queue.size() as the width

The original sin. Ignores ghosts entirely. Counts guests instead of hallway.

06 Pushing null children

NPE on the next pop, or β€” worse β€” a phantom trailing node that inflates rightMost and silently widens your answer.

07 Reading rightMost after the loop

You measure the next level. Off-by-one-level, which is much more confusing to debug than off-by-one-index.

08 Right-before-left ordering

In BFS this scrambles which node is "leftmost." In DFS it's fatal β€” your first[depth] records the rightmost node, and every width comes out ≀ 1 or negative.

09 DFS: overwriting first[depth] on every visit

It must be written only on first arrival (if (depth == list.size())). Overwrite it and every node becomes its own anchor, so every width computes to 1.

10 DFS: sloppy HashMap bookkeeping

Works, but an ArrayList indexed by depth is cleaner and makes the "first arrival" check a one-liner (depth == list.size()), which is much harder to get wrong under interview pressure.

11 Assuming indices are globally unique IDs

They're not, under per-level numbering β€” a node on level 2 and a node on level 5 can both be "index 3." That's fine, because we only ever compare within a level. But if you try to reuse these as map keys across levels, you'll be baffled.

12 Taking Math.max of the raw index across all levels

Meaningless. Width is a per-level quantity. Reset your anchors every single level.

⚠️ Common Pitfall β€” the one that fails the interview

Bug 3 is the one that separates candidates. Nearly everyone writes correct BFS. Far fewer notice that the index doubles per level and will overflow. If you say "indices double each level so they'd overflow around depth 31 β€” I'll normalize per level since I only need differences" before the interviewer asks, you have just answered the hidden question the problem was designed around.

09

How to Recognise This Pattern Again

Trigger words in the prompt

  • "width," "span," "spread," "distance between the ends"
  • "including the null nodes in between" β€” this phrase alone should make you write 2i / 2i+1 immediately, no thinking required
  • "as if the tree were a complete/perfect binary tree"
  • "position in the level," "same level," "leftmost and rightmost"

Structural tells

  • The answer depends on where nodes are, not just that they exist.
  • Missing children matter β€” a null changes the answer.
  • The problem is asking about geometry, not content.
πŸ’‘ The general rule to internalise

When a tree question is about POSITION rather than VALUE, stop treating the tree as pointers and start treating it as coordinates.

Same family, all solved by "assign numbers instead of following pointers":

ProblemThe coordinate you assign
Maximum width (this one)seat index: 2i, 2i+1
Vertical order traversalcolumn: xβˆ’1, x+1
Top view / bottom viewcolumn: xβˆ’1, x+1
Diagonal traversaldiagonal: col βˆ’ row
Left view / right viewdepth + first/last seen
⚠️ The companion rule

Any time you assign an index that doubles per level, you have signed up for an overflow bug. Normalize immediately. Don't wait for the test case to teach you.

10

Memory Hook

🧠 Memory Hook

"Don't count the guests β€” read their room numbers. The empty rooms count themselves."

And the backup hook, for the overflow half of the problem:

🧠 Memory Hook β€” the bellhop

"Room numbers don't matter. Only the hallway between them does. So restart the numbering on every floor."

If you remember nothing else from this lesson six months from now, remember Gary refusing to walk into empty rooms, and the bellhop slapping a "ROOM 0" sticker on every floor. Those two images regenerate the entire algorithm from scratch.

11

Interview Explanation β€” 60 seconds

The interviewer asks: "Walk me through your approach." Here is the answer, near-verbatim.

🎯 Say this

"Width here isn't the node count β€” it's the span including the nulls between the outermost nodes. So instead of trying to represent the nulls, I'll assign every node the index it would have in a perfect binary tree: a node at index i has children at 2i and 2i + 1. That way the missing nodes are just skipped integers β€” I never allocate them.

Then I BFS level by level, carrying (node, index) pairs. Because a queue preserves left-to-right order, the first node I pop on a level is the leftmost and the last is the rightmost, so the level's width is just last βˆ’ first + 1. I take the max across levels.

One catch: indices double each level, so they overflow around depth 31. But I only ever subtract indices within a level β€” I never need their absolute values. So at the start of each level I subtract the leftmost index from every node before generating children. That keeps the numbers bounded by the actual width while leaving all the differences intact.

O(n) time, O(w) space where w is the max level width. I can also do it with a preorder DFS, recording the first index seen at each depth β€” same idea, different traversal."

That last sentence is free points. It signals you understand the idea is traversal-independent, not that you memorised one implementation.

The three beats, if you forget the script

BeatThe one sentence
ReframeWidth is a span, not a count β€” so index the seats instead of counting nodes.
MechanismBFS with (node, index); FIFO order hands me leftmost and rightmost for free.
Hidden catchIndices double per level β†’ overflow β†’ normalize per level, since only differences matter.
12

Complexity

Where every unit of cost comes from O(n) time

Time: O(n)

Where does the cost come from? Each node is:

  • enqueued exactly once (by its parent, or as the root),
  • dequeued exactly once,
  • and does O(1) work when dequeued β€” one subtraction, two null checks, at most two pushes, one max.

There is no scanning, no sorting, no revisiting, no nested traversal. n nodes Γ— constant work = O(n).

πŸ’‘ The payoff, stated precisely

The ghost seats cost nothing. A level might span 1,000,000 seats and contain 2 nodes β€” you do 2 units of work, not 1,000,000. If you'd padded with nulls, time would be O(2h), which is catastrophically worse. Arithmetic beats materialisation.

Space: O(w), where w is the max number of actual nodes on any level

The queue holds one level plus part of the next.

ShapeQueue holdsSpaceStresses
Perfect treen/2 at the bottomO(n)memory
Skewed spine1–2 nodesO(1)overflow

Note the asymmetry: the deep tree is cheap on space but is the one that would have overflowed. The wide tree is expensive on space but never overflows. Two different trees stress two different parts of your solution β€” which is exactly why interviewers like this problem.

DFS variant

Time O(n) identically. Space O(h) for the recursion stack plus O(h) for the first[] list. Better on wide trees (O(log n) for balanced), worse on deep ones β€” a 10,000-deep tree will StackOverflowError. That's the real tradeoff, and it's a good thing to say out loud.

13

Java Implementation Walkthrough

Solution A β€” BFS with normalization write this one

Line by line

private static class Item β€” we need to carry two things through the queue: the node and its seat. Java has no tuple, and you can't stash the index on TreeNode. Alternatives: two parallel queues (fragile β€” they can desync), or Map<TreeNode,Integer> (slower and keyed on identity). A tiny pair class is the honest answer. static because it doesn't need the outer instance.

if (root == null) return 0; β€” guards the NPE and defines the empty case.

ArrayDeque over LinkedList β€” faster, no per-node allocation. Note ArrayDeque rejects nulls, which is a happy accident: if you ever accidentally enqueue a null it fails loudly instead of silently. LinkedList would let it through.

queue.offer(new Item(root, 0)) β€” root is seat 0. (Root could be 1 with global heap numbering; with per-level positions, 0 is natural and the 2i / 2i+1 recurrence still holds because positions within a level also double.)

int levelSize = queue.size(); β€” the fence. Freeze the level boundary before it moves.

int leftMost = queue.peek().index; β€” left finger and normalization anchor, both.

int rightMost = leftMost; β€” initialised so a level with one node computes leftMost βˆ’ leftMost + 1 = 1, correctly.

for (int i = 0; i < levelSize; i++) β€” exactly one level. i is a counter, nothing more; we never use it.

int norm = cur.index - leftMost; β€” the "ROOM 0" sticker. Applied to this node so its children inherit small numbers.

rightMost = cur.index; β€” plain overwrite. FIFO guarantees the last write is the rightmost node. It uses the un-normalized index because it's compared against leftMost, which is also un-normalized β€” same coordinate system on both sides of the subtraction. (You could equally use norm and compare against 0. Just don't mix the two.)

2 * norm and 2 * norm + 1 β€” the seat rule, applied to the normalized value so the shift propagates downward.

maxWidth = Math.max(...) β€” after the level, not during it. During it, you don't yet know rightMost.

Solution B β€” DFS with normalization mention this one

Why this works

depth == firstIndexAtDepth.size() β€” the list has entries for depths 0..sizeβˆ’1. If we've arrived at a depth equal to the size, we've never been here before. And since preorder always fully explores left before right, the first node ever visited at depth d is the leftmost node at depth d. That's the load-bearing guarantee. Flip the two recursive calls and the whole thing collapses.

norm + 1 β€” since firstIndexAtDepth.get(depth) is the leftmost, norm is the distance from it, and +1 makes it inclusive.

The normalization propagates the same way: children are generated from norm, and because every node at a given depth is shifted by a consistent amount, all differences survive.

⚠️ Common Pitfall

Delete the if (depth == size) guard and always overwrite, and every node becomes its own anchor: norm is always 0, and the answer is always 1. Silent, and maddening to find.

Which one do you actually write? decision table
BFSDFS
SpaceO(max level width)O(height)
Wide tree (perfect, 10⁡ nodes)O(n) queue β€” heavyO(log n) β€” great
Deep tree (skewed, 10⁡ nodes)O(1) queue β€” greatStackOverflowError ☠️
Finding leftmostfree (FIFO order)needs first-arrival bookkeeping
Interview defaultβœ… this onemention it, code it if asked

Default to BFS. It maps to the "level" language in the problem, the leftmost/rightmost extraction is free, and it can't blow the stack. Bring up DFS as the alternative β€” noting it's better on wide balanced trees, worse on deep ones β€” and you've shown you understand both.

14

Visual Cheat Sheet

The whole lesson, compressed to the size of an index card.

IdeaRemember
ObservationA node at position i has children at 2i and 2i+1. Gaps become skipped integers, not allocated objects.
Core formulawidth = rightmostIndex βˆ’ leftmostIndex + 1. Subtraction counts the ghosts for free.
PatternBFS (or preorder DFS) carrying a positional index alongside each node. Position-based tree problem, not value-based.
Data structureQueue<(node, index)> β€” needs a tiny pair class. DFS variant: an ArrayList mapping depth β†’ first index seen.
The critical trickIndex normalization β€” subtract leftMost at the start of every level, before generating children. Uniform shift preserves differences.
Pitfall 1queue.size() inside the loop condition β†’ levels bleed together. Snapshot levelSize first.
Pitfall 2Overflow. Indices double per level; int dies at depth 31, long at 63. Normalization β€” not a bigger type β€” is the fix.
Pitfall 3Forgetting +1; using queue.size() as the width; pushing null children.
Recognition cue"Width," "span," and especially "including the nulls in between" β†’ assign 2i / 2i+1 immediately.
Memory hook"Don't count the guests β€” read their room numbers. The empty rooms count themselves."
ComplexityTime O(n) β€” each node enqueued and dequeued once. Space O(max level width) for BFS, O(height) for DFS.
The movieGary the lazy carpet inspector at Hotel Binaria. Measures first guest to last guest, never enters an empty room, and the bellhop re-stickers "ROOM 0" on every floor so his clipboard doesn't catch fire.
Lesson complete

Type "next" when you're ready.

Or name the problem you want β€” the next lesson uses this exact structure.