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
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.
See it move
Green = visited, blue = in the queue, purple = popped now. The queue is shown front β back. L0, L1, β¦ are the depths.
root = [1,2,3,4,5,null,6,null,null,7]step 1/13- level
- -
- size
- -
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.
root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, k = 2step 1/3- k
- 2
- distance
- 0
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.
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
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.
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.
The order you need is sorted order, which is an inorder DFS. Level order mixes small and large values together.
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.
Sub-patterns
Same core idea, different rule for βvalidβ. Learn them in this order.
Level order
4 questionsReturn the nodes grouped by level, sometimes in a twisted order (bottom-up, zigzag) or for a tree with many children per node.
Level aggregates / views
5 questionsFold each level into one answer: the last node, the max, the average, the width, or a check about two nodes on the same level.
Distance / minimum depth
3 questionsUse BFS's "closest first" order: stop at the first leaf, or spread out from a node in every direction (adding parent links to go up).
Next pointers
2 questionsLink every node to the next node on its level. A level that is already linked is a linked list, so it can drive the linking of the level below.
Don't mix these up
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.
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.
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.
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.
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: 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.
// 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.
// 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 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;
}