Skip to content

4.14.0 — Trees: The Pattern

Recognition cue. The input is a tree. The real question is always the same one:

What does this node do, and what does it need from its children?

Answer that and the code writes itself. Almost every problem here is a traversal with different work done at the node.

The two directions

This is the frame that makes the whole chapter easy, and it is worth deciding before writing a line.

Information flowing UP — return it. Depths, sums, counts, validity. The node cannot answer until its children have. This is post-order.

Information flowing DOWN — pass it as a parameter. The maximum seen on the path, an allowed range, the depth so far. Each branch gets its own copy, so no undo is needed.

Several problems do both: a range goes down, a count comes up.

The one structure that catches people

For the harder problems, what the function returns is not what the problem asks for.

  • Diameter returns the depth, records the best turning point.
  • Maximum Path Sum returns the best one-sided sum, records the best two-sided sum.

The reason is always the same: a parent can only extend a path through one child, but the answer is allowed to turn around and use both. Two different numbers, so one is returned and the other kept on the side.

Return what the parent needs; record what the question wants.

The three templates

python
# 1. DFS, information up (post-order)
def solve(node):
    if not node: return base_value
    left = solve(node.left)
    right = solve(node.right)
    return combine(node.val, left, right)

# 2. DFS, information down
def solve(node, state):
    if not node: return
    new_state = update(state, node)
    solve(node.left, new_state)
    solve(node.right, new_state)

# 3. BFS, level by level
queue = deque([root])
while queue:
    for _ in range(len(queue)):        # ← freeze the level size FIRST
        node = queue.popleft()
        ...
        if node.left:  queue.append(node.left)
        if node.right: queue.append(node.right)

DFS or BFS?

usewhenspace
DFSthe answer depends on subtrees — depth, sums, paths, validationO(h)
BFSthe answer depends on levels, or you want the shallowest and can stop earlyO(w)

On a balanced tree the last level holds about half the nodes, so BFS is O(n) and DFS is O(\log n). On a degenerate tree it reverses. Say "height", not "log n", unless balance is promised.

The fifteen problems

#ProblemThe one insight
4.14.1Invert Binary Tree ★Swap here, trust the recursion below
4.14.2Maximum Depth ★1 + max(left, right)
4.14.3DiameterReturn depth, record the turning point
4.14.4Balanced Binary TreeOne pass, -1 as the failure sentinel
4.14.5Same Tree ★Recurse on both trees at once
4.14.6Subtree of Another Tree ★Same Tree tried at every node
4.14.7LCA of a BST ★Walk to the split point; ordering replaces searching
4.14.8Level Order Traversal ★Freeze len(queue) to bound the level
4.14.9Right Side View ★The last node of each level
4.14.10Count Good NodesCarry the path maximum down
4.14.11Validate BST ★Carry a (low, high) range down — subtrees, not children
4.14.12Kth Smallest in a BST ★In-order gives sorted output; stop at k
4.14.13Construct from Preorder + Inorder ★Pre-order gives the root, in-order gives the split
4.14.14Maximum Path Sum ★Clamp negative branches to zero
4.14.15Serialize and Deserialize ★Null markers make one traversal enough

★ marks the Blind 75 subset.

The traps on this pattern

Validating a BST by comparing parent and child. The property is about entire subtrees. See the counterexample on 4.14.11.

Forgetting to freeze the level size in BFS. Children added mid-loop change len(queue) and the level boundary is lost.

Edges versus nodes. Diameter counts edges; depth counts nodes. They differ by one and the problem statement decides which.

Recomputing depth at every node. That is the O(n^2) trap in Balanced Binary Tree; compute each depth once on the way up.

Assuming O(\log n) space. It is O(h), and h is n on a degenerate tree.

Using pop(0) as a queue. O(n) in Python. Use collections.deque.

What the interviewer will push on

"Does information flow up or down here?" Say it before coding. It picks the template.

"Why does your function return something different from the answer?" The parent can only extend one side.

"Why is checking parent against child not enough for a BST?" The counterexample.

"How do you know where a BFS level ends?" The frozen length.

"Which uses more memory, DFS or BFS?" O(h) against O(w), and which is worse depends on the tree's shape.

One thing to volunteer: state the recursion in one English sentence before writing it. "The depth of a tree is one more than the depth of its deeper subtree." If you cannot produce that sentence, the code will not be right either.

Recall

  • The question is always what does this node do, and what does it need from its children.
  • Up = return value (post-order). Down = parameter. Many problems use both.
  • Hard problems return one thing and record another, because a parent can extend only one side while the answer may turn around and use both.
  • BFS levels need the queue length frozen before the inner loop.
  • In-order on a BST gives sorted output — that fact solves validation, k-th smallest, and range queries.
  • Validate a BST with a (low, high) range carried down. Comparing parent to child is the classic wrong answer.
  • Space is O(h) for DFS and O(w) for BFS; on a balanced tree that is O(\log n) against O(n).
  • One traversal plus null markers determines a tree; without markers you need two traversals.

Next: 4.14.1 Invert Binary Tree — the simplest possible version of "do something here, then recurse".