2Brute2Best
πŸ”₯0

Tree BFS Pattern: Algorithmic Intuition, Sub-patterns & C++ Guide

Process the tree level by level with a queue. Everything about "levels", "rows", or "closest" is BFS.

4 sub-patterns Β· 14 questions

Remember this Β· Tree BFS
Trigger
Level order, per-level max/average, right side view, minimum depth, nodes at distance k.
Think
Does the question care about depth / rows rather than paths?
Key question
How many nodes are in the current level (queue size at the start of the level)?
A

What is this pattern?

Tree BFS visits a tree one level at a time: the root, then all nodes at depth 1, then depth 2, and so on. A queue makes this happen. Pop a node from the front, push its children to the back, and the children wait behind everything already in line.

The one trick that turns a plain BFS into a level-by-level BFS: at the start of each round, write down size = q.size() and pop exactly that many nodes. Those size nodes are one whole level, and every child pushed during the round belongs to the next one.

Because BFS reaches nodes in order of distance from where it started, it also answers "shortest" and "nearest" questions: the first leaf it pops is the shallowest one. Each node is pushed and popped once, so it is O(n) time, and the queue holds at most one level: O(width) space.

A family photo taken generation by generation. The photographer calls the grandparents first. While they stand for the photo, their children line up behind them. Only when every grandparent is done does the next generation step forward. Counting heads before each photo is the size snapshot: it tells you where one generation ends.

B

See it move

Green = visited, blue = in the queue, purple = popped now. The queue is shown front β†’ back. L0, L1, … are the depths.

Level order: one round per levelroot = [1,2,3,4,5,null,6,null,null,7]step 1/13
tree
L0L1L2L31245736
queue
0
1
β–²front
State
level
-
size
-
levels
[ ]
Start: the queue holds the root

BFS keeps a queue of nodes waiting to be visited. Green = done, blue = waiting in the queue, purple = the node popped right now.

Dashed arrows = parent links (orange when used this round). Blue = current BFS round, green = visited earlier, purple = the answer.

Distance K: BFS in every directionroot = [3,5,1,6,2,0,8,null,null,7,4], target = 5, k = 2step 1/3
tree + parent links (dashed)
35target6274108
queue
0
5
State
k
2
distance
0
visited
5
Parent links added; BFS starts at target 5

A tree only points down, but nodes at distance 2 can be above the target too. So first record each node's parent (dashed arrows), then treat the tree as a graph: neighbours = left, right and parent.

C

How do I recognise it?

  • "Level by level", "level order", "each row", "each depth" in a tree.

  • Something per level: the average, the largest value, the first or last node, the width.

  • "Right side view" / "left side view": the last (or first) node of each level.

  • "Minimum depth", "nearest leaf", "shortest" anything in a tree: BFS stops at the first hit.

  • "Distance K from a node" or "time to spread from a node": BFS from that node, using parent links to also go up.

  • "Connect each node to the next node on its right" (next pointers).

Looks like Tree BFS, but isn't

Maximum depth, path sums, diameter, balanced
β†’ Tree DFS

These combine answers from the children (bottom-up) or carry state down one path (top-down). Levels don't matter, and DFS uses only O(height) memory.

Shortest path in a grid or a general graph
β†’ Graph BFS

Same queue idea, but a graph has cycles, so you always need a visited set. In a tree that only goes downward, nothing can be reached twice.

Kth smallest in a BST, validate a BST
β†’ Tree DFS (BST properties)

The order you need is sorted order, which is an inorder DFS. Level order mixes small and large values together.

D

How would I discover it from scratch?

Don't memorise β€œuse tree bfs”. Ask these questions in order β€” the pattern falls out.

If the answer is grouped by depth (rows, levels, "seen from the side"), visit the tree in that order. A queue gives you exactly that: nodes come out in the order they were found, and nodes are found level by level.

E

Sub-patterns

Same core idea, different rule for β€œvalid”. Learn them in this order.

F

Don't mix these up

Tree BFS (queue, by level) vs Tree DFS (recursion, by path)

Ask what the answer is grouped by. Rows, depths, "seen from the side", nearest β†’ BFS. A value combined from the two subtrees, or something carried along a root-to-leaf path β†’ DFS. Some (right side view, max depth) can be done both ways; pick the one that reads naturally.

Minimum depth (BFS, stop at the first leaf) vs Maximum depth (DFS, 1 + max)

Max depth can use 1 + max(left, right) because a missing child never wins a max. Min depth can't: min would pick the missing side (0), and that is not a leaf. BFS avoids the trap and stops early.

`size = q.size()` taken once vs `i < q.size()` in the loop

The queue grows while you pop, because children are pushed. Reading q.size() in the loop condition lets the next level leak into this round. Take the snapshot before the loop.

Tree BFS (no visited set) vs BFS with parent links (visited set needed)

Going only downward, no node can be reached twice. As soon as you add parent links, the tree becomes an undirected graph, and without visited you bounce between a node and its parent.

G

Code templates (C++)

Level order with the size snapshot

The base of every question here. size is taken before the inner loop, so children pushed during the round can't sneak into it.

level-order-template.cpp
// Level order: snapshot the queue size, pop exactly that many β†’ one level.
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> levels;
    if (!root) return levels;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int size = q.size();                      // nodes on this level, fixed before we push children
        vector<int> level;
        for (int i = 0; i < size; i++) {
            TreeNode* node = q.front(); q.pop();
            level.push_back(node->val);
            if (node->left) q.push(node->left);   // children belong to the next round
            if (node->right) q.push(node->right);
        }
        levels.push_back(level);
    }
    return levels;
}

Fold each level into one value

Same rounds, but keep one value per level: the last node (right side view) or the max (start from the first node's value, never 0). Average and width follow the same shape.

level-aggregate-template.cpp
// Per-level answers: the same rounds, but fold each level into one value.
// Here: the right side view (last node of each round) and the largest value per row.
vector<int> rightSideView(TreeNode* root) {
    vector<int> view;
    if (!root) return view;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int size = q.size();
        for (int i = 0; i < size; i++) {
            TreeNode* node = q.front(); q.pop();
            if (i == size - 1) view.push_back(node->val);   // last in its round = seen from the right
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    return view;
}

vector<int> largestValues(TreeNode* root) {
    vector<int> best;
    if (!root) return best;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int size = q.size();
        int mx = q.front()->val;                    // start from a real value, never 0
        for (int i = 0; i < size; i++) {
            TreeNode* node = q.front(); q.pop();
            mx = max(mx, node->val);
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        best.push_back(mx);
    }
    return best;
}

Distance from a node: parent map + BFS

Record parents with one DFS, then BFS from the start node over left, right and parent, with a visited set. After k rounds the queue holds exactly the nodes k edges away.

bfs-distance-template.cpp
// Distance from a start node in every direction: add parent links, then BFS like a graph.
// Returns the values of all nodes exactly k edges away from start.
vector<int> nodesAtDistance(TreeNode* root, TreeNode* start, int k) {
    unordered_map<TreeNode*, TreeNode*> parent;
    function<void(TreeNode*)> link = [&](TreeNode* node) {
        for (TreeNode* child : {node->left, node->right})
            if (child) { parent[child] = node; link(child); }
    };
    if (root) link(root);

    queue<TreeNode*> q;
    unordered_set<TreeNode*> seen{start};       // without this we walk straight back where we came from
    q.push(start);
    for (int dist = 0; !q.empty(); dist++) {
        if (dist == k) {                         // everything in the queue is exactly k away
            vector<int> out;
            while (!q.empty()) { out.push_back(q.front()->val); q.pop(); }
            return out;
        }
        int size = q.size();
        for (int i = 0; i < size; i++) {
            TreeNode* node = q.front(); q.pop();
            TreeNode* up = parent.count(node) ? parent[node] : nullptr;
            for (TreeNode* nb : {node->left, node->right, up})
                if (nb && seen.insert(nb).second) q.push(nb);
        }
    }
    return {};
}

Next pointers in O(1) space

Walk the current level through next and append its children to the level below with a dummy head and a tail. Works for any tree shape (LC 117), and so for perfect trees too (LC 116).

next-pointers-template.cpp
// Next pointers in O(1) space: walk the current level through its `next` links
// while building the level below with a dummy head. Works for any shape of tree.
// struct Node { int val; Node *left, *right, *next; };   (given by LeetCode)
Node* connect(Node* root) {
    Node* levelStart = root;
    while (levelStart) {
        Node dummy(0);                            // dummy.next will be the first node of the level below
        Node* tail = &dummy;
        for (Node* cur = levelStart; cur; cur = cur->next) {   // this level is already linked
            if (cur->left)  { tail->next = cur->left;  tail = tail->next; }
            if (cur->right) { tail->next = cur->right; tail = tail->next; }
        }
        levelStart = dummy.next;                  // go down one level
    }
    return root;
}