2Brute2Best
🔥0

Binary Tree Level Order Traversal

Medium·LeetCode #102Basic Anchor
LeetCode
30-Second Mental ModelTree BFS, Level order (size snapshot per round)
Complexity:O(N · H)→O(N)
Key Insight

Use a queue, and split it into rounds. At the start of each round, take size = q.size() before pushing any children. Those size nodes are exactly one row. Pop that many, push their children to the back, and the next round starts with the next row.

Core Validity Invariant

The queue holds exactly one row when a round starts, so popping size nodes takes out that whole row and nothing from the row below it.

01

Problem

You are given the root of a binary tree. Return the node values one row at a time: first the root's row, then the row below it, and so on. Inside each row, read the nodes from left to right.

Each row becomes one inner list in the answer. An empty tree gives an empty answer.

Input

The root of a binary tree. It can be empty (null).

In the examples, a tree is written in level order: the root first, then each row left to right. A null marks a missing child, so [3, 9, 20, null, null, 15, 7] means 9 and 20 are the root's children, and 9 has no children.

Output

A list of rows. The first list is the root's row, the next list holds its children, and so on. Each row is read left to right.

Example 1
Input: root = [3, 9, 20, null, null, 15, 7]
Output: [[3], [9, 20], [15, 7]]

Row 0 is 3. Row 1 is its children 9 and 20. Row 2 is 15 and 7, the children of 20 (9 has none).

Example 2
Input: root = [1]
Output: [[1]]

A single node is one row.

Example 3
Input: root = []
Output: []

No nodes, so no rows.

Constraints
  • • The number of nodes in the tree is in the range [0, 2000].
  • • −1000 ≤ Node.val ≤ 1000
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    Is a row of the tree a set of nodes at the same depth? What order would I need to visit nodes in to print one row at a time?

  2. 2.

    Does depth-first search naturally visit the nodes row by row, or does breadth-first search?

  3. 3.

    If I visit a node and then its children, how would I know where one row stops and the next row starts?

  4. 4.

    Could I walk the whole tree once for each row and keep only the nodes at that depth? What would that cost?

  5. 5.

    When one row is about to start, how many nodes belong to it? Can I know that before I pop any of them?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceFor each depth d, walk the whole tree and keep the nodes at depth dO(N · H)O(H)
OptimalQueue-based BFS; take size = q.size() at the start of each round and pop exactly that manyO(N)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “level by level”Rows are depths. A question about rows is a breadth-first question, not a path question.
  • “left to right inside each level”A queue gives this order for free, because children are pushed left, then right.
  • “return a list of lists”One inner list per row, so the number of rounds equals the number of rows.
Tree grouped by depth+queue (FIFO) BFS+one round = one snapshot of the queue size→Tree BFS, Level order (size snapshot per round)

The question asks for rows, not paths, so Tree BFS is the right family. The key rule is take size at the start of each round and pop exactly that many. The row below always sits behind the current row in the queue, so the snapshot is the only thing that marks the boundary. Binary Tree Level Order II, N-ary Level Order and Zigzag all reuse this same loop.

10

Don't make this mistake

Reading `q.size()` inside the loop condition
✗ for (int i = 0; i < q.size(); i++) {
✓ int size = q.size(); for (int i = 0; i < size; i++) {

Pattern confusion: the loop condition is checked after every pop, and each pop can push two children. The queue grows with the row below, so the loop keeps going past this row's boundary and the next row bleeds into this one. On [3, 9, 20, null, null, 15, 7] the live version gives [[3, 9], [20, 15], [7]]. After 3 is popped, the queue holds [9, 20] (size 2), so the loop also pops 9 and files it under row 0.

Pushing null children
✗ q.push(node->left); q.push(node->right);
✓ if (node->left) q.push(node->left); if (node->right) q.push(node->right);

A null pointer goes into the queue, and the next pop reads ->val of nullptr and crashes. Node 9 in the example has no children, so the wrong version queues two nulls right behind 20.

Forgetting the empty tree
✗ queue<TreeNode*> q; q.push(root);
✓ if (!root) return levels; queue<TreeNode*> q; q.push(root);

An empty tree must return []. Without the check, a null root sits in the queue and crashes on its first pop.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Tree BFS translate into production architectures and low-level memory mechanics.