Tree Depth First Search
Recurse down, collect on the way back up. The call stack IS your backtracking — no explicit undo needed. Two types: root-to-leaf paths and any-node paths.
Iterative path tracking requires explicit undo. Messy. Recursive DFS backtracks for free. Natural.
The insight: recursion gives you a free undo. When you append to a path list before the recursive call and pop after it, the path automatically reflects exactly the nodes on the current root-to-node path — no manual bookkeeping needed.
Two Tree DFS Types
Know which type before writing a single line.
Path starts at root and must reach a leaf. Carry info (sum, path list, number) downward. Check condition at the leaf (both children null).
if not node.left and not node.right:
return accumulated == targetPath can start and end anywhere (but only goes downward). Use prefix sum HashMap: for each node, count paths ending here by checking (current_sum - target) in the map.
count = prefix_map.get(curr - target, 0) prefix_map[curr] = prefix_map.get(curr,0)+1 # ... recurse ... prefix_map[curr] -= 1 # backtrack
The 3-Step Framework
The 3-step DFS skeleton — works for both types with one change.
Two base cases every time. (1) Null node: return False/0/None immediately. (2) Leaf node (no children): check your condition and return. The leaf check IS the answer for root-to-leaf problems.
if not node: return False # null
if not node.left and not node.right:
return node.val == remaining # leaf checkRoot-to-leaf: pass updated state (remaining-=node.val, path.append, number=number*10+val) into children. Any-path prefix: update the HashMap, recurse both children, then undo the HashMap update.
# Root-to-leaf: carry downward path.append(node.val) dfs(node.left, remaining - node.val, path) dfs(node.right, remaining - node.val, path) path.pop() # backtrack # Return upward: collect from children left_h = dfs(node.left) right_h = dfs(node.right) return 1 + max(left_h, right_h)
Diameter and max path sum need BOTH children's contributions to update the answer, but can only RETURN one arm upward. Use a nonlocal variable for the global max updated inside the recursion.
max_diam = 0
def height(node):
nonlocal max_diam
lh = height(node.left)
rh = height(node.right)
max_diam = max(max_diam, lh + rh) # use both
return 1 + max(lh, rh) # return onePattern Recognition
DFS vs BFS — two seconds to decide.