Pattern family: Binary Tree / BST / Tree Traversal / Construction Difficulty range: Easy β Hard Language: Java
- How to identify this pattern β start here
- What is this pattern?
- Core rules
- 2-Question framework
- Variants table
- Universal Java template
- Quick reference cheatsheet
- Common mistakes
- Complexity summary
| If you see this... | It means |
|---|---|
| "inorder / preorder / postorder traversal" | DFS traversal β recursive or iterative |
| "level order / BFS traversal" | BFS β queue-based level by level |
| "zigzag / spiral order" | BFS + direction flag per level |
| "invert / mirror / symmetric" | swap left and right children recursively |
| "lowest common ancestor" | LCA β path meeting point |
| "validate BST" | inorder must be strictly increasing |
| "path sum / root to leaf" | DFS with running sum |
| "maximum path sum" | DFS with local max, update global max on return |
| "construct tree from traversals" | preorder/inorder β find root, split, recurse |
| "serialize / deserialize tree" | BFS or preorder + null markers |
| "diameter / depth / balanced" | DFS returning height, check condition |
| "Kth smallest in BST" | inorder traversal = sorted order |
| "recover / validate BST" | find out-of-order nodes in inorder |
| "convert sorted array/list to BST" | binary mid as root, recurse halves |
Brute force: collect all nodes β sort β answer.
β O(n log n) time, O(n) space.
β BST property gives O(log n) search for free β use it.
β Tree structure = natural recursion β subproblem at every node.
Key insight: most tree problems decompose as:
solve(root) = f(solve(root.left), solve(root.right), root.val)
This is the post-order return pattern β solve children first, combine at root.
Signal 1 β Input is a tree node / root
All tree problems start with
TreeNode root. Recursion is almost always the right approach.
Signal 2 β "Left and right" appear symmetrically in the problem
Invert, symmetric, same tree β swap/compare left and right at every node.
Signal 3 β "Path" from root to leaf or any node to any node
Root-to-leaf: pass running sum down. Any-to-any: track max at each node, return single branch up.
Signal 4 β BST + "search / insert / kth smallest / validate"
Use BST property: left < root < right. Inorder = sorted. No need to visit all nodes.
Signal 5 β "Construct tree from" two traversal arrays
Preorder[0] = root. Find root in inorder β split into left/right subtrees. Recurse.
Problem involves a Binary Tree?
β
βΌ
What are you doing?
β
βββ VISIT all nodes in order β Traversal (DFS/BFS)
β βββ Inorder/Pre/Post β DFS recursive or stack-based iterative
β βββ Level order β BFS with queue
β
βββ COMPARE or TRANSFORM structure β Mirror & Symmetry
β βββ Invert, Same Tree, Symmetric, Subtree
β
βββ SEARCH or USE BST property β BST Operations
β βββ Search, LCA of BST, Kth smallest, Validate, Recover
β
βββ CHECK a property β Validation & Properties
β βββ Balanced, Diameter, Depth, Completeness, Tilt
β
βββ ACCUMULATE along a path β Path Sum
β βββ Pass sum down (rootβleaf) or return max up (any path)
β
βββ BUILD a tree β Construction
βββ From traversals, sorted array/list, serialize/deserialize
TreeNode structure:
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
}Three core traversal orders:
1
/ \
2 3
/ \
4 5
Inorder (L, Root, R): 4 2 5 1 3 β BST gives sorted order
Preorder (Root, L, R): 1 2 4 5 3 β root first β used for construction
Postorder(L, R, Root): 4 5 2 3 1 β children before root β used for cleanup/height
Level order (BFS): 1 2 3 4 5 β row by row
Two fundamental recursive patterns:
// Pattern 1: TOP-DOWN (pass info down)
// Use when: parent needs to tell children something (running sum, bounds)
void dfs(TreeNode node, int param) {
if (node == null) return;
// use param at this node
dfs(node.left, newParam);
dfs(node.right, newParam);
}
// Pattern 2: BOTTOM-UP (return info up)
// Use when: parent needs result from children first (height, max path)
int dfs(TreeNode node) {
if (node == null) return base;
int left = dfs(node.left);
int right = dfs(node.right);
// combine left + right + node.val
return result;
}BFS (Level Order):
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size(); // snapshot of current level size
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
}Rule 1 β Always handle null first
// WRONG β NullPointerException when node is null
int height(TreeNode node) {
return 1 + Math.max(height(node.left), height(node.right)); // crashes on null
// CORRECT β base case first
int height(TreeNode node) {
if (node == null) return 0; // base case
return 1 + Math.max(height(node.left), height(node.right));
}Rule 2 β For BST problems, use BST property (avoid full scan)
// WRONG β O(n) scan, ignores BST structure
boolean search(TreeNode root, int target) {
if (root == null) return false;
return search(root.left, target) || root.val == target || search(root.right, target);
// CORRECT β O(log n) using BST property
boolean search(TreeNode root, int target) {
if (root == null) return false;
if (root.val == target) return true;
return target < root.val
? search(root.left, target) // go left
: search(root.right, target); // go right
}Rule 3 β For "maximum path sum" use a global variable, return single branch
// The path can go through any node. At each node:
// - update global max using BOTH branches (left + root + right)
// - return only ONE branch up (can't take both directions to parent)
int maxSum = Integer.MIN_VALUE;
int dfs(TreeNode node) {
if (node == null) return 0;
int left = Math.max(0, dfs(node.left)); // ignore negative branches
int right = Math.max(0, dfs(node.right));
maxSum = Math.max(maxSum, left + node.val + right); // update global (both sides)
return node.val + Math.max(left, right); // return single side up
}Rule 4 β For construction from traversals, use a HashMap for inorder indices
// WRONG β O(n) search in inorder array for each root β O(nΒ²) total
int rootIdx = 0;
for (int i = 0; i < inorder.length; i++)
if (inorder[i] == root) { rootIdx = i; break; }
// CORRECT β O(1) lookup with HashMap β O(n) total
Map<Integer, Integer> inorderMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) inorderMap.put(inorder[i], i);Rule 5 β Use iterative inorder with stack for BST in-place operations
// Iterative inorder = Morris traversal alternative; clean with explicit stack
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) { stack.push(curr); curr = curr.left; }
curr = stack.pop();
// process curr here
curr = curr.right;
}| Answer | Pattern |
|---|---|
| Visit in a specific order | Traversal (DFS/BFS) |
| Compare left and right subtrees | Mirror & Symmetry |
| Use left < root < right | BST Operations |
| Check height/balance/depth | Validation & Properties (bottom-up) |
| Accumulate along path | Path Sum (top-down or bottom-up) |
| Build the tree structure | Construction |
| Direction | Pattern | Example |
|---|---|---|
| Top-down (parent β children) | Pass constraints/state down | Validate BST (pass min/max bounds), Path Sum (pass remaining sum) |
| Bottom-up (children β parent) | Return computed result up | Height, diameter, max path sum |
| Both | Combine results at every node | LCA (return node if found), balanced check |
| Level by level (BFS) | Process row by row | Level order, zigzag, check completeness |
Decision shortcut:
- "check / validate property" β bottom-up, return from children
- "path from root" β top-down, pass running sum
- "any path between two nodes" β bottom-up + global variable
- "BST operation" β use left < root < right, O(log n)
- "level by level" β BFS with queue, snapshot level size
- "construct tree" β preorder root + inorder split
Common core:
TreeNodewith left/right children, recursive traversal What differs: traversal order, what you pass down, what you return up
| Variant | Direction | Data structure | Key operation | Return |
|---|---|---|---|---|
| Inorder / Pre / Post | DFS (bottom-up or top-down) | call stack | visit at right moment | void or list |
| Level Order | BFS | Queue | snapshot q.size() per level |
list of lists |
| Zigzag | BFS | Queue + flag | reverse alternate levels | list of lists |
| Invert / Mirror | top-down | call stack | swap left/right | root |
| Same Tree / Symmetric | both-directions | call stack | compare pair of nodes | boolean |
| LCA Binary Tree | bottom-up | call stack | return node if found | TreeNode |
| LCA BST | top-down | β | use BST property | TreeNode |
| Validate BST | top-down | β | pass (min, max) bounds | boolean |
| Recover BST | inorder scan | two pointers | find swapped pair | void |
| Height / Depth | bottom-up | call stack | 1 + max(L, R) |
int |
| Diameter | bottom-up + global | global int | L + R at each node |
int |
| Balanced | bottom-up | call stack | return -1 on imbalance | int (-1 or height) |
| Path Sum (rootβleaf) | top-down | call stack | pass sum - node.val |
boolean |
| Path Sum II | top-down | backtrack list | add/remove node | list of lists |
| Path Sum III | prefix sum + map | HashMap | map.get(curr - target) |
int count |
| Max Path Sum | bottom-up + global | global int | update global, return one side | int |
| Construct from traversals | divide & conquer | HashMap | root in preorder, split inorder | TreeNode |
| Serialize / Deserialize | BFS or preorder | Queue/string | null markers | string / TreeNode |
| Kth Smallest BST | inorder | counter | stop at kth | int |
// βββ TEMPLATE A: DFS Traversals ββββββββββββββββββββββββββββββββββββββββββββββ
// Recursive
void inorder(TreeNode node, List<Integer> res) {
if (node == null) return;
inorder(node.left, res);
res.add(node.val); // β move this line for pre/post order
inorder(node.right, res);
}
// Iterative Inorder (interview-safe β no recursion stack overflow)
List<Integer> inorderIterative(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) { stack.push(curr); curr = curr.left; }
curr = stack.pop();
res.add(curr.val);
curr = curr.right;
}
return res;
}
// βββ TEMPLATE B: BFS Level Order βββββββββββββββββββββββββββββββββββββββββββββ
List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size(); // snapshot β critical!
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
level.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
res.add(level);
}
return res;
}
// βββ TEMPLATE C: Bottom-Up (return height / property) ββββββββββββββββββββββββ
int height(TreeNode node) {
if (node == null) return 0;
int left = height(node.left);
int right = height(node.right);
return 1 + Math.max(left, right);
}
// βββ TEMPLATE D: Top-Down with bounds (Validate BST) βββββββββββββββββββββββββ
boolean isValidBST(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return isValidBST(node.left, min, node.val)
&& isValidBST(node.right, node.val, max);
}
// Call: isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE)
// βββ TEMPLATE E: Path Sum (top-down, rootβleaf) ββββββββββββββββββββββββββββββ
boolean hasPathSum(TreeNode node, int target) {
if (node == null) return false;
if (node.left == null && node.right == null) return node.val == target; // leaf
return hasPathSum(node.left, target - node.val)
|| hasPathSum(node.right, target - node.val);
}
// βββ TEMPLATE F: Max Path Sum (bottom-up + global) βββββββββββββββββββββββββββ
int maxSum = Integer.MIN_VALUE;
int maxPathSum(TreeNode node) {
if (node == null) return 0;
int left = Math.max(0, maxPathSum(node.left)); // ignore negative
int right = Math.max(0, maxPathSum(node.right));
maxSum = Math.max(maxSum, left + node.val + right); // update: can use both sides
return node.val + Math.max(left, right); // return: only one side
}
// βββ TEMPLATE G: LCA Binary Tree βββββββββββββββββββββββββββββββββββββββββββββ
TreeNode lca(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lca(root.left, p, q);
TreeNode right = lca(root.right, p, q);
if (left != null && right != null) return root; // p in one side, q in other
return left != null ? left : right; // both in same side
}
// βββ TEMPLATE H: Construct from Preorder + Inorder βββββββββββββββββββββββββββ
Map<Integer, Integer> inorderMap = new HashMap<>();
int preIdx = 0;
TreeNode build(int[] preorder, int left, int right) {
if (left > right) return null;
int rootVal = preorder[preIdx++];
TreeNode root = new TreeNode(rootVal);
int mid = inorderMap.get(rootVal);
root.left = build(preorder, left, mid - 1);
root.right = build(preorder, mid + 1, right);
return root;
}
// βββ TEMPLATE I: Path Sum III (prefix sum) βββββββββββββββββββββββββββββββββββ
int pathSumIII(TreeNode root, int target) {
Map<Long, Integer> prefixCount = new HashMap<>();
prefixCount.put(0L, 1); // empty path
return dfs(root, 0L, target, prefixCount);
}
int dfs(TreeNode node, long curr, int target, Map<Long, Integer> map) {
if (node == null) return 0;
curr += node.val;
int count = map.getOrDefault(curr - target, 0);
map.merge(curr, 1, Integer::sum);
count += dfs(node.left, curr, target, map);
count += dfs(node.right, curr, target, map);
map.merge(curr, -1, Integer::sum); // backtrack
return count;
}
// βββ TEMPLATE J: Serialize / Deserialize (Preorder + nulls) ββββββββββββββββββ
public String serialize(TreeNode root) {
if (root == null) return "null";
return root.val + "," + serialize(root.left) + "," + serialize(root.right);
}
public TreeNode deserialize(String data) {
Queue<String> q = new LinkedList<>(Arrays.asList(data.split(",")));
return buildTree(q);
}
TreeNode buildTree(Queue<String> q) {
String val = q.poll();
if (val.equals("null")) return null;
TreeNode node = new TreeNode(Integer.parseInt(val));
node.left = buildTree(q);
node.right = buildTree(q);
return node;
}
// βββ TEMPLATE K: Kth Smallest in BST βββββββββββββββββββββββββββββββββββββββββ
int k, result;
void kthSmallest(TreeNode node) {
if (node == null) return;
kthSmallest(node.left);
if (--k == 0) { result = node.val; return; }
kthSmallest(node.right);
}
// βββ TEMPLATE L: Convert Sorted Array to BST βββββββββββββββββββββββββββββββββ
TreeNode sortedArrayToBST(int[] nums, int left, int right) {
if (left > right) return null;
int mid = left + (right - left) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = sortedArrayToBST(nums, left, mid - 1);
root.right = sortedArrayToBST(nums, mid + 1, right);
return root;
}SIGNAL IN PROBLEM β PATTERN β KEY CODE
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
"inorder / preorder / postorder" β DFS traversal β recursive or iterative with stack
"level order / row by row" β BFS β queue + snapshot size each level
"zigzag / spiral order" β BFS + flag β reverse alternate levels
"invert / mirror binary tree" β top-down swap β swap left/right, recurse both
"symmetric tree / same tree" β dual DFS β compare pair of nodes simultaneously
"lowest common ancestor" β bottom-up LCA β return root if found on both sides
"LCA of BST" β BST property β go left/right based on values
"validate BST" β top-down bounds β pass (min, max), check at each node
"kth smallest in BST" β inorder + counter β inorder is sorted; stop at k
"path sum root to leaf" β top-down β pass target - node.val down
"path sum any node to any node" β prefix sum + map β map.get(curr - target)
"maximum path sum" β bottom-up + global β update global (L+root+R); return max one side
"diameter of binary tree" β bottom-up + global β L+R at each node, return 1+max(L,R)
"balanced binary tree" β bottom-up β return -1 on imbalance, height otherwise
"construct from traversals" β divide & conquer β preorder[0]=root; split inorder; recurse
"serialize / deserialize" β preorder + null mark β "1,2,null,null,3,null,null"
"convert sorted array/list to BST" β binary mid as root β mid=root, recurse left/right halves
Burn these into memory:
// Base case β ALWAYS handle null first
if (node == null) return 0/false/null;
// Level order β snapshot size BEFORE loop
int size = q.size();
for (int i = 0; i < size; i++) { ... }
// Validate BST β pass Long bounds, not int
isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE)
// Max path sum β ignore negative children
int left = Math.max(0, dfs(node.left));
// Path Sum III β backtrack after recursion
map.merge(curr, -1, Integer::sum); // undo after left+right
// LCA β both sides found means current node IS the LCA
if (left != null && right != null) return root;// BUG β NullPointerException
int height(TreeNode node) {
return 1 + Math.max(height(node.left), height(node.right)); // node could be null!
// FIX
int height(TreeNode node) {
if (node == null) return 0;
return 1 + Math.max(height(node.left), height(node.right));
}// BUG β processes more than one level per iteration
while (!q.isEmpty()) {
TreeNode node = q.poll(); // doesn't separate levels!
// FIX β snapshot size before inner loop
while (!q.isEmpty()) {
int size = q.size(); // how many nodes in this level
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
...
}
}// BUG β Integer.MIN_VALUE and MAX_VALUE can equal node values
boolean isValidBST(TreeNode node, int min, int max) { ... }
// FIX β use Long
boolean isValidBST(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
...
}// BUG β trying to return both sides to parent (invalid path)
return node.val + left + right; // a path cannot fork at two nodes above
// FIX β return only the better single branch
maxSum = Math.max(maxSum, left + node.val + right); // update global with both
return node.val + Math.max(left, right); // return one side only// BUG β O(n) search in inorder for every recursive call β O(nΒ²)
for (int i = 0; i < inorder.length; i++)
if (inorder[i] == rootVal) { mid = i; break; }
// FIX β precompute HashMap β O(1) lookup β O(n) total
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < inorder.length; i++) map.put(inorder[i], i);
int mid = map.get(rootVal);// BUG β prefixCount keeps growing, counts paths from previous branches
dfs(node.left, curr, target, map);
dfs(node.right, curr, target, map);
// forgot to remove curr from map after visiting!
// FIX β backtrack after both children return
map.merge(curr, -1, Integer::sum); // undo this node's contribution// BUG β if both p and q are in left subtree, right returns null but left returns LCA
// Actually this is handled correctly by:
return left != null ? left : right;
// But the mistake is checking root == p before searching:
// WRONG β returns p even if q is deeper in p's subtree
if (root == null || root == p || root == q) return root;
// This is actually CORRECT for LCA β if we find p (or q), we return it
// The calling code handles the rest. This is a valid approach.| Problem | Time | Space | Notes |
|---|---|---|---|
| Any DFS traversal | O(n) | O(h) | h = height; O(log n) balanced, O(n) skewed |
| BFS level order | O(n) | O(w) | w = max width; O(n) worst case |
| Invert / Same / Symmetric | O(n) | O(h) | visit every node once |
| LCA Binary Tree | O(n) | O(h) | may visit all nodes |
| LCA BST | O(h) | O(h) | BST property; O(log n) balanced |
| Validate BST | O(n) | O(h) | visit every node once |
| Recover BST | O(n) | O(h) | inorder scan for two swapped nodes |
| Kth Smallest BST | O(h + k) | O(h) | stop early in inorder |
| Height / Depth | O(n) | O(h) | post-order |
| Diameter | O(n) | O(h) | post-order with global max |
| Balanced | O(n) | O(h) | post-order; early exit with -1 |
| Path Sum I | O(n) | O(h) | top-down; stop at leaf |
| Path Sum II | O(nΒ²) | O(n) | O(n) paths Γ O(n) copy each |
| Path Sum III | O(n) | O(n) | prefix sum + HashMap |
| Max Path Sum | O(n) | O(h) | post-order with global max |
| Construct from traversals | O(n) | O(n) | HashMap for O(1) inorder lookup |
| Serialize / Deserialize | O(n) | O(n) | preorder with null markers |
| Convert Sorted Array to BST | O(n) | O(log n) | binary mid recursion |
| Convert Sorted List to BST | O(n) | O(log n) | inorder simulation |
General rules:
- Space is O(h) for DFS β O(log n) balanced tree, O(n) worst case (skewed).
- BFS space is O(w) β O(n/2) = O(n) for the last level of a complete tree.
- BST operations are O(log n) average for balanced trees.
- Path Sum III uses prefix sum β O(n) vs O(nΒ²) naive approach.
- Construction from traversals: HashMap makes it O(n) vs O(nΒ²) linear search.
Trees appear in 20β25% of FAANG interview problems. Master the 4-line BFS template and the bottom-up DFS return pattern β they cover 80% of all tree problems.