Trees — Master Recap & Pattern Cheatsheet

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

Trees Section Complete — 74 Problems (346–420)

9 Core Tree Patterns

Pattern 1: DFS Preorder/Inorder/Postorder

def dfs(node):
    if not node: return
    # preorder: process here
    dfs(node.left)
    # inorder: process here
    dfs(node.right)
    # postorder: process here

Pattern 2: BFS Level Order

from collections import deque
def bfs(root):
    q = deque([root])
    while q:
        for _ in range(len(q)):
            node = q.popleft()
            # process
            if node.left:  q.append(node.left)
            if node.right: q.append(node.right)

Pattern 3: Tree DP (return pair/tuple)

def dp(node):
    if not node: return base_case
    left = dp(node.left)
    right = dp(node.right)
    # combine and return
    global_max = max(global_max, combine(left, right))
    return value_for_parent(left, right, node)

Pattern 4: BST Property (min/max bounds)

def validate(node, mn=float('-inf'), mx=float('inf')):
    if not node: return True
    if not (mn < node.val < mx): return False
    return validate(node.left, mn, node.val) and            validate(node.right, node.val, mx)

Pattern 5: LCA (Lowest Common Ancestor)

def lca(root, p, q):
    if not root or root == p or root == q: return root
    left = lca(root.left, p, q)
    right = lca(root.right, p, q)
    return root if left and right else left or right

Pattern 6: Path Problems

# Path sum with prefix sums
def pathSum(root, targetSum):
    prefix = {0: 1}
    def dfs(node, cur):
        if not node: return 0
        cur += node.val
        count = prefix.get(cur - targetSum, 0)
        prefix[cur] = prefix.get(cur, 0) + 1
        count += dfs(node.left, cur) + dfs(node.right, cur)
        prefix[cur] -= 1
        return count
    return dfs(root, 0)

Pattern 7: Serialize/Deserialize

def serialize(root):
    def dfs(node):
        if not node: return ['#']
        return [str(node.val)] + dfs(node.left) + dfs(node.right)
    return ','.join(dfs(root))

def deserialize(data):
    vals = iter(data.split(','))
    def build():
        val = next(vals)
        if val == '#': return None
        node = TreeNode(int(val))
        node.left, node.right = build(), build()
        return node
    return build()

Pattern 8: Binary Lifting (Kth Ancestor)

LOG = 16
up = [[-1]*LOG for _ in range(n)]
for i in range(n): up[i][0] = parent[i]
for j in range(1, LOG):
    for i in range(n):
        if up[i][j-1] != -1:
            up[i][j] = up[up[i][j-1]][j-1]

Pattern 9: Rerooting Technique

# DFS1: compute from one root
# DFS2: recompute all others in O(n) using formula
# dist[child] = dist[parent] - cnt[child] + (n - cnt[child])

Problem Index by Pattern

PatternProblems
DFS BasicMax Depth, Invert, Symmetric, Same Tree, Diameter
BFSLevel Order, Zigzag, Right View, Max Width
Tree DPMax Path Sum, House Robber III, Cameras, Distribute Coins
BSTValidate, Kth Smallest, Delete Node, Insert, Recover BST
LCALCA Binary Tree, LCA BST, All Nodes Distance K
Path SumPath Sum I/II/III/IV, Sum Root to Leaf
SerializeSerialize BT, Serialize BST
ConstructionBuild from Pre+In, BST from Preorder, Unique BSTs II
Binary LiftingKth Ancestor
RerootingSum of Distances

Complexity Quick Reference

OperationBST avgBST worstBalanced
SearchO(log n)O(n)O(log n)
InsertO(log n)O(n)O(log n)
DeleteO(log n)O(n)O(log n)
In-order traversalO(n)O(n)O(n)
HeightO(log n)O(n)O(log n)

Key Rules

  1. Post-order when a node needs info from both subtrees (Tree DP, max path sum)
  2. Pre-order when passing info DOWN (path tracking, serialization)
  3. In-order for BST → gives sorted sequence; reverse in-order → descending
  4. BFS for level-based problems, shortest path in unweighted tree
  5. Parent pointers to make tree bidirectional for "all-directions" BFS
  6. BST bounds (min, max) for validation and construction problems
  7. Catalan number = count of unique BST structures with n nodes

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro