AlgoVision ALP

Tree Patterns

The reusable skeletons behind every tree problem. Learn these once, solve them all.

▶ Decision Framework

When do I use BFS vs DFS?

▸ BFS (Breadth-First)

  • Need values level by level (#102, #103, #107)
  • Need the shortest path or first leaf (#111 iterative)
  • Comparing trees in lockstep (#100, #572 iterative)
  • State lives in an explicit queue

▸ DFS (Depth-First)

  • Computing a bottom-up value (depth, equality)
  • Need to aggregate results from subtrees (#104, #111)
  • Collecting values with a level index (#102, #103, #107 recursive)
  • State lives in the call stack
▶ BFS vs DFS Decision Flowchart
Tree Problem
Need levels?
YES
BFS + Queue
NO
Bottom-up value?
YES
Recursive DFS
NO
Compare structure?
YES
BFS/DFS with pairs
NO
Recursive DFS
Iterative vs Recursive — what's the real difference?

They're the same algorithm with different state storage. Neither is "better" — interviewers want to see you can do both.

Iterative

  • You manage a queue or stack explicitly
  • State is visible — you can inspect it
  • Easier to animate step by step
  • Uses O(n) heap memory

Recursive

  • The call stack manages state for you
  • State is implicit — hidden in stack frames
  • Often shorter and more elegant code
  • Uses O(h) stack memory (h = tree height)

Interview tip: Start with whichever feels natural, then offer to show the other. Saying "I can also do this iteratively with a queue" signals depth.

◆ Universal Patterns

Pattern 1: The Null Check Trinity all problems

Nearly every recursive tree solution starts with 1–3 null checks. Recognise the shape and you've solved the first half.

# Base case — empty tree if not root: return 0 # or [], True, False — depends on problem # Single-child guard (depth problems) if not root.left: # only right subtree exists return 1 + self.minDepth(root.right) # Two-tree comparison (#100 Same Tree) if not p and not q: # both null → match return True if not p or not q: # one null → mismatch return False

Muscle memory: Before writing anything else, ask yourself: "What happens when the node is null?"

Pattern 2: Level Index Trick DFS

The shared skeleton behind #102, #103, and #107. One DFS with a level parameter, three different results.

def traverse(self, root): result = [] def dfs(node, level): if not node: return if level == len(result): # ← first node at this depth result.append(???) # ← [] or deque() result[level].???(node.val) # ← append or appendleft dfs(node.left, level + 1) dfs(node.right, level + 1) dfs(root, 0) return ??? # ← result, result[::-1], or convert

Fill in the ??? slots for each problem:

Pattern 3: Deque Power Moves BFS & DFS

from collections import deque — this one import unlocks O(1) operations at both ends.

BFS Queue

q = deque([root]) node = q.popleft()
O(1) popleft vs O(n) list.pop(0)

Zigzag Level

level = deque() level.appendleft(val)
O(1) insert at front for R←L

Bottom-Up

result = deque() result.appendleft(lvl)
O(1) prepend vs O(n) insert(0)

Rule of thumb: If you ever write list.pop(0), list.insert(0, x), or list.reverse(), ask yourself if a deque eliminates the O(n) cost.

Pattern 4: Post-Order Aggregation DFS

For problems that compute a value from subtrees (depth, diameter, balance), the skeleton is always post-order: recurse first, combine after.

def solve(self, root): if not root: return 0 left = self.solve(root.left) # recurse left right = self.solve(root.right) # recurse right return 1 + ???(left, right) # combine

The single-child guard in #111 prevents counting a path that ends at a non-leaf (e.g., a node with only one child isn't a valid "minimum depth" endpoint).

■ Problem Quick Reference

LeetCode #100 • Easy
Same Tree
Compare two trees node-by-node. BFS uses a queue of pairs.
queue = deque([(p, q)])
Unique pattern — two-tree input
LeetCode #572 • Easy
Subtree of Another Tree
Outer BFS + inner isSameTree at each node. Combines #100 as helper.
if self.isSameTree(node, subRoot):
Builds on #100 — outer traversal + inner comparison
LeetCode #102 • Medium
Level Order Traversal
The foundation. Every other level-order variant modifies this.
result.append(current_level)
Baseline — #107 and #103 diff against this
LeetCode #103 • Medium
Zigzag Level Order
Same as #102 but alternate append vs appendleft per level.
level = deque() + parity flag
vs #102: adds direction toggle
LeetCode #107 • Medium
Level Order Bottom-Up
Identical to #102 but reverse the result at the end.
return result[::-1]
vs #102: literally one line different
LeetCode #104 • Easy
Maximum Depth
Post-order DFS: recurse both sides, return 1 + max.
return 1 + max(left, right)
vs #111: no single-child guard needed
LeetCode #111 • Easy
Minimum Depth
Like #104 but must guard against single-child nodes.
if not root.left: recurse right only
vs #104: adds 2 guard clauses
AlgoVision — Tree Patterns • See the code. Trace the logic. Ace the interview.