Pattern #16
Tree DFS
EssentialDefine what each subtree returns to its parent.
When to use
Binary tree answers that combine left and right results: depth, validity, LCA, path sums.
Recognition cues
- Max depth / balanced tree
- Validate BST / LCA
- Path sum variants
Common pitfalls
- Mixing global answer updates with return values carelessly
- BST validation with wrong bounds
- Not handling null children as base case
90-second recognition drill
Which pattern fits best?
- Max depth / balanced tree
- Validate BST / LCA
- Path sum variants
Interactive
Mental model
A full worked walkthrough of the invariant. Pause, scrub the dots, or use ← →. Aim to narrate each step yourself.
post-order: children first, then me
Tree DFS walks structure, not a flat list. Root 3, left 9, right subtree 20 → 15, 7.
How to think about it
Write a function f(node) that returns what the parent needs (height, is-BST flag, min/max, best path through node). Solve left and right first, then combine. Sometimes also update a global best.
The interactive mental model draws the actual tree (edges + nodes), not a flat list: watch values and badges (heights, LCA markers) flow up the structure.
Template shapes
| Shape | Core move | Notes |
|---|---|---|
| Return height | 1+max(L,R) | Depth, balance |
| Return multi-field | struct of flags | BST / diameter |
| Pass bounds | low < val < high | Validate BST |
Complexity baseline
O(n) time visits each node once; space O(h) recursion stack.
From template to problem
- Specify f(node)’s return type in one sentence.
- Base case for null.
- Recurse left/right; combine; optionally update global answer.
- Return the value the parent requires.
Template
Same skeleton in TypeScript, Python, and C#. Adapt the invariant; keep the structure.
/** Tree DFS template: max depth. */
export class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val; this.left = left; this.right = right;
}
}
export function maxDepth(root: TreeNode | null): number {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
/** Tree DFS template: max depth. */
export class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val; this.left = left; this.right = right;
}
}
export function maxDepth(root: TreeNode | null): number {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
- 1#98 Validate Binary Search TreeGuidemedium
- 2#100 Same TreeGuideeasy
- 3#104 Maximum Depth of Binary TreeGuideeasy
- 4#110 Balanced Binary TreeGuideeasy
- 5#124 Binary Tree Maximum Path SumGuidehard
- 6#236 Lowest Common Ancestor of a Binary TreeGuidemedium