Tree — Concepts & Patterns
Scope — Tree concepts, tree types and traversal-order strategy — the why and which, plus the advanced techniques that are not per-pattern templates (Morris threading, binary lifting, re-rooting). The templates themselves live in tree2.md. See also — deep dives split out of this file: tree_lca_distance.md — LCA, node distance, parent maps and root-to-leaf path templates; tree_codec.md — subtree serialization and tree ⟷ string codecs; tree_construction.md — building a tree from traversals, strings and index ranges; tree_examples.md — the worked LC archive for the patterns taught here. Neighbouring sheets: tree2.md — one numbered, copy-paste template per pattern; binary_tree.md — binary-tree DFS state-flow and structural templates; bst.md — ordered trees; tree_backtrack.md — root→leaf path problems that undo state on the way back up.
LeetCode Problem Lists
Time Complexity
| Data structure | Search | Insert | Delete | Min/Max |
|---|---|---|---|---|
| Tree (general) | O(n) | O(n) | O(n) | O(n) |
General tree (no ordering guarantee) — every operation may visit all nodes. A balanced tree drops these to O(log n). Space is O(n) for storage plus O(h) for the recursion stack. For an ordered tree with O(log n) operations see bst.md.
Overview
Tree is a hierarchical data structure consisting of nodes connected by edges, with one root node and no cycles. Trees are fundamental in computer science for organizing data efficiently.
Key Properties
- Nodes: Elements that store data and references to children
- Root: The topmost node with no parent
- Leaves: Nodes with no children
- Height: Distance from root to deepest leaf
- Depth: Distance from root to a specific node
- Complexity: see the Time Complexity table above
Tree Array Representation
Trees can be efficiently represented using arrays, especially for complete binary trees:
# Tree Structure
1
/ \
2 3
/ \
4 5
# Array Representation: [1, 2, 3, 4, 5]
# Index mapping:
# - Root at index 0
# - For node at index i:
# - Left child at index 2*i + 1
# - Right child at index 2*i + 2
# - Parent at index (i-1)/2
References
0) Core Concepts
0-1) Tree Types Classification
Basic Tree Types
| Type | Description | Key Properties | Use Cases |
|---|---|---|---|
| General Tree | Node with any number of children | Flexible structure | File systems, org charts |
| Binary Tree | Each node has ≤ 2 children | Simple structure, recursive | Expression trees, decision trees |
| Complete Binary Tree | All levels filled except possibly last | Efficient array representation | Heaps, priority queues |
| Perfect Binary Tree | All levels completely filled | 2^h - 1 nodes | Theoretical analysis |
| BST | Left < Root < Right ordering | O(log n) search/insert/delete | Search operations, databases |
| Heap | Parent-child ordering property | Fast min/max extraction | Priority queues, sorting |
| Trie | Prefix tree for strings | Efficient string operations | Auto-complete, spell check |
0-2) Common Tree Patterns Priority 4 of 5 — High value — a gap here costs you rounds
The nine recurring shapes. Each row names the one place the code for that pattern lives — no pattern is written out twice in this family of sheets.
| # | Pattern | Core idea | Code lives in | Examples |
|---|---|---|---|---|
| 1 | Path-Based | carry the accumulated value (sum, max-so-far, path) DOWN through DFS parameters | tree_lca_distance.md — root-to-leaf path templates | LC 112, 113, 257, 437, 1448 |
| 2 | Subtree Validation | post-order — validate both children before deciding for the parent | tree2 1.3) | LC 98, 101, 110 |
| 3 | Height vs Depth | height is computed bottom-up (post-order); depth is carried top-down (pre-order) | 0-3) Top-Down vs Bottom-Up | LC 104, 111, 543 |
| 4 | Tree Construction | one traversal gives the structure, the other gives the position; or split an index range at the chosen root | tree_construction.md | LC 105, 106, 654, 108 |
| 5 | Serialization | encode = DFS that returns a string; decode = recursive descent that consumes a prefix | tree_codec.md | LC 297, 449, 606, 536 |
| 6 | Move Parent | build a parent map, treat the tree as an undirected graph, then BFS in all directions | tree_lca_distance.md — move-parent pattern | LC 863, 742, 1740 |
| 7 | Node Path | fingerprint each subtree as val,left,right with # for null, then hash the string |
tree_codec.md | LC 652, 572, 508 |
| 8 | Node Deletion with State | carry isDeleted for the node and isParentDeleted for its parent; a survivor whose parent died becomes a forest root |
tree_examples.md — LC 1110 | LC 1110, 1325, 669 |
| 9 | Get Node Distance | pre-order DFS carrying depth DOWN, returning it UP on a hit; -1 is the not-found sentinel because 0 is a valid answer |
tree_lca_distance.md — distance between nodes | LC 1740, 863, 1123 |
Patterns 2 and 3 are the two halves of every tree recursion, so they are spelled out below; the other seven are one hop away in the sheet named in the table. Reference (pattern 2): Subtree Validation Video
0-3) Top-Down vs Bottom-Up DFS — Two Strategies for Tree Problems Priority 5 of 5 — Must know — expect it in almost every loop

Reference: MaximumDepthOfBinaryTree.java
Core Distinction:
- Top-down: Pass state down from parent to children via parameters. The answer accumulates during traversal (pre-order position).
- Bottom-up: Collect results up from children to parent via return values. The answer is built after subtrees are solved (post-order position).
Top-Down (Pre-order) Bottom-Up (Post-order)
───────────────────── ──────────────────────
1 ← start here 1 ← combine here
/ \ pass depth=1 / \ return heights
2 3 depth=2 2 3 left=1, right=1
/ \ depth=3 / \ left=2, right=0
4 5 → update global max 4 5 → return max+1
Pattern 1: Top-Down (pass state down, pre-order)
The parent passes accumulated state (depth, path, max-so-far) to children. Typically uses a global variable or output parameter to collect the final answer.
// LC 104 — Top-Down: pass depth down, update global max
// 3 variants: (a) void helper + global var, (b) void helper + depth param, (c) return depth param
// Variant A: void helper + global var (simplest top-down)
int maxDepth = 0;
public int maxDepth_topDown(TreeNode root) {
dfs(root, 1); // start at depth 1
return maxDepth;
}
private void dfs(TreeNode root, int depth) {
if (root == null) return;
// Pre-order position: update answer with current state
maxDepth = Math.max(maxDepth, depth);
// Pass depth+1 DOWN to children
dfs(root.left, depth + 1);
dfs(root.right, depth + 1);
}
Pattern 2: Bottom-Up (collect results up, post-order)
Each node asks its children for their results, then combines them. The return value carries the answer upward. No global variable needed.
// LC 104 — Bottom-Up: children return their depth, parent adds 1
public int maxDepth_bottomUp(TreeNode root) {
if (root == null) return 0;
// Post-order: solve children FIRST
int leftDepth = maxDepth_bottomUp(root.left);
int rightDepth = maxDepth_bottomUp(root.right);
// Combine: take max of children, add 1 for current node
return 1 + Math.max(leftDepth, rightDepth);
}
Comparison:
| Aspect | Top-Down | Bottom-Up |
|---|---|---|
| Direction | Root → Leaves (pre-order) | Leaves → Root (post-order) |
| State passing | Via parameters (depth, path, max) | Via return values |
| Global variable | Often needed | Usually not needed |
| Return type of helper | Often void |
Returns computed value |
| Mental model | “What do I know so far?” | “What did my children find?” |
| Code simplicity | More verbose (extra params) | More concise |
When to Use Which:
Use TOP-DOWN when:
→ You need to pass parent/ancestor info to children
→ Path tracking: carry path, sum, or max-so-far downward
→ Early termination: can stop when condition met at a node
→ Examples: LC 112 (Path Sum), LC 129 (Sum Root to Leaf),
LC 1448 (Count Good Nodes), LC 257 (Binary Tree Paths)
Use BOTTOM-UP when:
→ Answer depends on BOTH children's results
→ Need to compute subtree properties (height, size, balance)
→ Validation: check property holds for entire subtree
→ Examples: LC 104 (Max Depth), LC 110 (Balanced Tree),
LC 543 (Diameter), LC 124 (Max Path Sum),
LC 236 (LCA), LC 652 (Find Duplicate Subtrees),
LC 968 (Binary Tree Cameras)
LC Problems by Strategy:
| LC # | Problem | Top-Down | Bottom-Up | Notes |
|---|---|---|---|---|
| 104 | Maximum Depth | Yes | Yes | Both work; bottom-up is simpler |
| 111 | Minimum Depth | Yes | Yes | Bottom-up needs null-child guard |
| 110 | Balanced Binary Tree | - | Yes | Must check subtree heights first |
| 112 | Path Sum | Yes | - | Carry remaining sum downward |
| 113 | Path Sum II | Yes | - | Top-down + backtracking |
| 124 | Max Path Sum | - | Yes | Combine left+right at each node |
| 129 | Sum Root to Leaf Numbers | Yes | - | Carry running number downward |
| 236 | Lowest Common Ancestor | - | Yes | Find targets in subtrees first |
| 257 | Binary Tree Paths | Yes | - | Carry path string downward |
| 543 | Diameter of Binary Tree | - | Yes | Track max(left+right) globally |
| 968 | Binary Tree Cameras | - | Yes | Greedy 3-state: 0=uncovered, 1=camera, 2=covered |
| 1448 | Count Good Nodes | Yes | - | Carry max-so-far downward |
Hybrid Pattern: Bottom-Up + Global Variable
Some problems use bottom-up return values to compute subtree info, but also maintain a global variable to track a cross-subtree answer (e.g., diameter, max path sum).
// LC 543 — Diameter: bottom-up height + global max update
int diameter = 0;
public int diameterOfBinaryTree(TreeNode root) {
height(root);
return diameter;
}
private int height(TreeNode root) {
if (root == null) return 0;
int left = height(root.left); // bottom-up: get children's height
int right = height(root.right);
// Global update: diameter passes THROUGH this node
diameter = Math.max(diameter, left + right);
// Return value: height of subtree (for parent to use)
return 1 + Math.max(left, right);
}
Interview Tip:
LC 104 (Max Depth) is the best problem to practice both strategies. Start with bottom-up (3 lines), then rewrite as top-down (global var + void helper). Understanding both unlocks the full tree problem toolkit.
0-4) Traversal Order Selection Strategy Priority 5 of 5 — Must know — expect it in almost every loop
When to use which traversal:
1. No specific root processing needed?
→ Any order works (preorder/inorder/postorder)
2. Need parent data for children?
→ Use PREORDER (root → left → right)
3. Need children data for parent?
→ Use POSTORDER (left → right → root)
4. Processing sorted data (BST)?
→ Use INORDER (left → root → right)
5. Level-by-level processing?
→ Use BFS/Level-order traversal
6. Need to move upward (to parent) or explore all directions?
→ Use MOVE PARENT pattern (Build parent map + BFS)
7. Need to compare or identify subtrees?
→ Use NODE PATH pattern (Subtree serialization with post-order)
Pre-order vs Post-order for Leaf Collection (LC 872)
Reference: LeafSimilarTrees.java
When collecting leaf nodes (e.g., LC 872 Leaf-Similar Trees), any DFS order that visits left before right produces the same left-to-right leaf sequence. However, there are practical differences:
Pre-order (recommended for leaf collection):
private void getLeafSeq(TreeNode root, List<Integer> list) {
if (root == null) return;
// Check leaf BEFORE recursing into children
if (root.left == null && root.right == null) {
list.add(root.val);
return; // ← Early exit: skip 2 unnecessary null-child calls
}
getLeafSeq(root.left, list);
getLeafSeq(root.right, list);
}
Post-order (also correct, but slightly wasteful):
private void getLeafSeq(TreeNode root, List<Integer> list) {
if (root == null) return;
getLeafSeq(root.left, list); // ← calls null, returns immediately
getLeafSeq(root.right, list); // ← calls null, returns immediately
// Check leaf AFTER recursing (children were both null)
if (root.left == null && root.right == null) {
list.add(root.val);
}
}
Why both produce the same result: The leaf sequence depends only on left-before-right visitation order, NOT on when the leaf check happens. Since a leaf has no children, post-order’s recursive calls to null return immediately before the leaf check — the leaf is still added in the same left-to-right order.
Why pre-order is preferred:
| Aspect | Pre-order | Post-order |
|---|---|---|
| Leaf sequence | Left → Right | Left → Right (same) |
| Early exit at leaf | Yes (return after adding) |
No (already recursed into null children) |
| Unnecessary null calls per leaf | 0 | 2 |
| Best for | Leaf collection, path building | Height, subtree properties |
Interview answer:
“I chose pre-order because it allows an immediate return once a leaf is identified, avoiding two redundant recursive calls to null children. Any DFS that visits left before right produces the same leaf sequence.”
Related problems where traversal order matters for leaf/path collection:
| LC # | Problem | Recommended Order | Why |
|---|---|---|---|
| 872 | Leaf-Similar Trees | Pre-order | Early exit at leaf |
| 257 | Binary Tree Paths | Pre-order | Build path top-down |
| 112 | Path Sum | Pre-order | Carry remaining sum down |
| 104 | Maximum Depth | Post-order | Need children’s height first |
| 110 | Balanced Binary Tree | Post-order | Validate subtree heights |
0-5) Traversal Quick-Reference Table (Interview Cheat Sheet) Priority 4 of 5 — High value — a gap here costs you rounds
Inspired by LC 113 Path Sum II — key insight: the traversal choice determines the algorithm structure.
| Traversal | Order | Core Use Case | When to Choose |
|---|---|---|---|
| Pre-order | root → left → right | Build path top-down | root-to-leaf paths, carry parent info to children, DFS + backtrack |
| Post-order | left → right → root | Compute subtree results bottom-up | height/depth, subtree sum, max path, DP on trees |
| In-order | left → root → right | Process nodes in sorted order | BST validation, kth smallest, sorted traversal |
| BFS | level by level | Level-by-level processing | min depth, zigzag, right side view, connect next pointer |
Interview Quick-Check Tips
Step 1 — What does the problem ask for?
| Problem asks for… | Use |
|---|---|
| All root-to-leaf paths / path with sum | Pre-order DFS + backtracking |
| Count paths (any start/end) with target sum | Pre-order DFS + prefix sum HashMap |
| Tree height / max depth | Post-order DFS |
| Subtree property (sum, size, max) | Post-order DFS |
| Identify / compare subtrees by structure | Post-order DFS + serialize val,left,right + HashMap |
| Find duplicate subtrees | Post-order DFS + subtree serialization + HashMap count |
| BST sorted order / kth smallest | In-order DFS |
| Validate BST | In-order DFS |
| Level-by-level / min depth | BFS |
| Connect same-level nodes | BFS |
Interview Trick (from LC 113):
If the problem asks for “root → leaf path”, it is almost always pre-order DFS + backtracking.
Interview Trick (from LC 437):
If the path does NOT need to start/end at root/leaf and asks for count, use Pre-order DFS + Prefix Sum HashMap (the “2-sum on tree” pattern). The full template — why the map must be undone on the way back up, the two spellings of the base case, and the
longoverflow trap in Java — is Template 14 in prefix_sum_advanced.md.
Classic LC Problems by Traversal Type
Pre-order DFS + Backtracking (root → leaf path)
| LC # | Problem | Key Idea |
|---|---|---|
| 112 | Path Sum | Pre-order DFS, check leaf with remaining sum |
| 113 | Path Sum II | Pre-order DFS + backtrack, collect all paths |
| 257 | Binary Tree Paths | Pre-order DFS + backtrack, build string paths |
| 437 | Path Sum III | Pre-order DFS + prefix sum HashMap, 2-sum trick: check (curSum-target) in map — template |
| 129 | Sum Root to Leaf Numbers | Pre-order DFS, carry running number |
| 404 | Sum of Left Leaves | Pre-order DFS, carry an isLeft flag down; add value only at a leaf reached as a left child |
Post-order DFS (bottom-up subtree computation)
| LC # | Problem | Key Idea |
|---|---|---|
| 104 | Maximum Depth of Binary Tree | Post-order, return max(left, right) + 1 |
| 543 | Diameter of Binary Tree | Post-order, track max left+right at each node |
| 124 | Binary Tree Maximum Path Sum | Post-order, track global max through root |
| 110 | Balanced Binary Tree | Post-order, return height or -1 if unbalanced |
| 572 | Subtree of Another Tree | Post-order serialization or recursive match |
| 236 | Lowest Common Ancestor | Post-order, return node when both targets found |
| 652 | Find Duplicate Subtrees | Post-order + serialize subtree → val,left,right + HashMap |
| 968 | Binary Tree Cameras | Post-order greedy, 3 states: uncovered/camera/covered |
| 563 | Binary Tree Tilt | Post-order, return subtree SUM upward while accumulating abs(leftSum - rightSum) into a global — classic “return one thing, collect another” |
In-order DFS (BST / sorted order)
| LC # | Problem | Key Idea |
|---|---|---|
| 98 | Validate Binary Search Tree | In-order, check ascending order |
| 230 | Kth Smallest Element in BST | In-order traversal, count to k |
| 501 | Find Mode in BST | In-order, track current/prev with count |
| 538 | Convert BST to Greater Tree | Reverse in-order (right → root → left) |
| 700 | Search in a Binary Search Tree | In-order search leveraging BST property |
BFS / Level-order
| LC # | Problem | Key Idea |
|---|---|---|
| 102 | Binary Tree Level Order Traversal | BFS with queue, collect each level |
| 111 | Minimum Depth of Binary Tree | BFS, return level when first leaf found |
| 116 | Populating Next Right Pointers | BFS level-order, connect siblings |
| 199 | Binary Tree Right Side View | BFS, take last node of each level |
| 103 | Zigzag Level Order Traversal | BFS + alternate direction per level |
| 117 | Populating Next Right Pointers II | Level linking on a NON-perfect tree — dummy-head sweep, O(1) space (see Template 4-1) |
| 637 | Average of Levels in Binary Tree | BFS, sum each level then divide by levelSize |
| 987 | Vertical Order Traversal | Tag (col, row, val), sort col → row → val (see Template 4-2) |
1) Tree Templates & Algorithms
1.1) Universal Tree Template Priority 5 of 5 — Must know — expect it in almost every loop
Core Principle: Tree problems are naturally recursive - solve for current node using solutions from subtrees.
# Universal Tree Template
def solve_tree_problem(root, params):
# Base case
if not root:
return base_case_value
# Process current node (preorder position)
process_current_node(root, params)
# Recursively solve subtrees
left_result = solve_tree_problem(root.left, updated_params)
right_result = solve_tree_problem(root.right, updated_params)
# Combine results (postorder position)
result = combine_results(root, left_result, right_result)
return result
// Java Universal Tree Template
public ResultType solveTreeProblem(TreeNode root, ParamType params) {
// Base case
if (root == null) {
return defaultValue;
}
// Preorder: Process current node
processCurrentNode(root, params);
// Recursive calls
ResultType leftResult = solveTreeProblem(root.left, updatedParams);
ResultType rightResult = solveTreeProblem(root.right, updatedParams);
// Postorder: Combine results
ResultType result = combineResults(root.val, leftResult, rightResult);
return result;
}
1.2) Template Selection Guide Priority 4 of 5 — High value — a gap here costs you rounds
| Pattern | Template | When to Use | Example Problems |
|---|---|---|---|
| DFS Recursive | Standard recursion | Most tree problems | LC 104, 110, 226 |
| DFS Iterative | Stack-based | Avoid recursion depth limits | LC 94, 144, 145 |
| BFS Level-order | Queue-based | Level processing needed | LC 102, 199, 515 |
| Divide & Conquer | Bottom-up recursion | Need subtree results | LC 124, 543, 687 |
| Path Tracking | DFS with path state | Path-related problems | LC 112, 257, 437 |
| Move Parent | Parent map + BFS | Bidirectional exploration | LC 863, 742, 1740 |
| Node Path | Subtree serialization | Subtree comparison/detection | LC 652, 572 |
1.3) Core Operations
1.3.1) Tree Traversal Strategies
Two Main Approaches:
-
Depth-First Search (DFS) - Go deep before going wide
- Preorder: Root → Left → Right (top-down processing)
- Inorder: Left → Root → Right (sorted order for BST)
- Postorder: Left → Right → Root (bottom-up processing)
-
Breadth-First Search (BFS) - Process level by level
- Level-order: Process all nodes at depth d before depth d+1

1.4) Choosing a Traversal
The four basic traversals are written out in tree2.md — that sheet is the numbered template catalogue, in Python and Java, one per pattern. What belongs here is the question that comes first: which one.
| The answer depends on… | Traversal | Because | Template |
|---|---|---|---|
| the parent, before the children are known | Pre-order — root → left → right | state flows down: a path, a depth, a running prefix | tree2 1.1) — LC 144 |
| the tree being a BST, and you want sorted order | In-order — left → root → right | in-order on a BST is the sorted sequence, which is why LC 98 and LC 230 are one line each | tree2 1.2) — LC 94 |
| both children, before the node can answer | Post-order — left → right → root | state flows up: a height, a sum, a “is this subtree valid” verdict | tree2 1.3) — LC 145 |
| distance from the root, or a per-level answer | Level-order (BFS) | the first time you meet a node is via a shortest path, and a level is a queue-length snapshot | tree2 1.4) — LC 102 |
| the level, but alternating direction | BFS + direction flag | reverse the row, do not reverse the traversal | tree2 1.5) — LC 103 |
The one-line test: ask “can this node answer without hearing from its children?” Yes → pre-order. No → post-order. “I need the whole row at once” → BFS.

1.4-1) Traversals that do not need the queue
Two techniques get an ordinary traversal’s answer without its space cost. They are not per-pattern templates, so they stay here rather than in the catalogue:
- O(1)-space level linking — when the node already has a
nextpointer, the level itself can act as the queue. Written out at tree2 8.1) — LC 116 / LC 117. - Coordinate-annotated traversal — carry
(row, col)through any traversal and sort at the end; the traversal order stops mattering, which is what makes DFS and BFS interchangeable for vertical order, top/bottom view, and LC 662-style width indexing. tree2 8.3) — LC 987. - Morris traversal — below.
Template 5: Morris Traversal (O(1) Space Tree Traversal)
In-order traversal with O(1) space using threaded binary tree
Core Concept: Morris Traversal uses each node’s empty right pointer as a temporary thread back to its in-order successor, enabling O(n) time, O(1) space traversal without a recursion stack.
# In-order Morris Traversal — O(n) time, O(1) space
def inorderMorris(root):
result = []
curr = root
while curr:
if not curr.left:
result.append(curr.val)
curr = curr.right
else:
# Find in-order predecessor
pred = curr.left
while pred.right and pred.right is not curr:
pred = pred.right
if not pred.right: # Thread: set predecessor → curr
pred.right = curr
curr = curr.left
else: # Unthread: restore tree
pred.right = None
result.append(curr.val)
curr = curr.right
return result
# Pre/post-order variants follow the same thread-manipulation pattern.
// Java — Morris In-Order (LC 94)
// time = O(N), space = O(1)
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
TreeNode current = root;
while (current != null) {
if (current.left == null) {
result.add(current.val);
current = current.right;
} else {
TreeNode pred = current.left;
while (pred.right != null && pred.right != current)
pred = pred.right;
if (pred.right == null) { // first visit: create thread
pred.right = current;
current = current.left;
} else { // second visit: unthread + process
pred.right = null;
result.add(current.val);
current = current.right;
}
}
}
return result;
}
Performance: O(n) time, O(1) space. Use when interviewer asks for O(1) space traversal. Pre-order variant: process node on first visit (when creating thread) instead of second. Post-order: requires reversing right spines — complex, rarely asked; prefer iterative stack.
| Traversal Method | Time | Space | Modifies Tree |
|---|---|---|---|
| Recursive | O(n) | O(h) | No |
| Iterative Stack | O(n) | O(h) | No |
| Morris | O(n) | O(1) | Temporarily (restored) |
Morris threading family — temporary thread vs. permanent rewire
Both the Morris traversal above and the O(1) flatten (LC 114) share the same core step: from the current node, find the rightmost node of its left subtree (the in-order predecessor) and use its empty right pointer to “thread” somewhere. They differ only in what they do with that thread:
| Variant | Thread points to | Restored? | Purpose | Example |
|---|---|---|---|---|
| Morris traversal | in-order successor (curr) |
✅ yes (unthread on 2nd visit) | Visit nodes O(1) space | LC 94, 144 |
| Morris rewire (flatten) | original right subtree (curr.right) |
❌ no (permanent) | Restructure tree in-place | LC 114 |
# Morris REWIRE pattern — permanent threading (LC 114 Flatten Binary Tree to Linked List)
# time = O(n), space = O(1)
def flatten(root):
curr = root
while curr:
if curr.left:
# find left subtree's rightmost node (in-order predecessor)
rightmost = curr.left
while rightmost.right:
rightmost = rightmost.right
# splice: predecessor.right -> original right subtree (PERMANENT, not restored)
rightmost.right = curr.right
curr.right = curr.left # move left subtree to the right
curr.left = None # clear left
curr = curr.right # advance down the new right spine
Mental model: for each node with a left child, the left subtree is “inserted” between the node and its original right subtree, because the left subtree’s pre-order traversal must come immediately after the node and before the right subtree. The rightmost node of the left subtree is exactly where the right subtree should re-attach.
curr curr
/ \ \
L R ───► L (curr.right = curr.left)
\ \
... ...
\ \
L_rightmost L_rightmost
\
R (L_rightmost.right = R)
The recursive counterpart — post-order where each call returns the tail of the subtree it flattened, so the parent knows where to splice — is worked through in tree_examples 16), including why the tail is checked right → left → node.
When to reach for this: any “in-place, O(1) space, restructure a tree along its right spine” problem. The
while rightmost.rightpredecessor-finding step is the signature. Recognize it as the same machinery as Morris traversal — only the thread’s destination and whether you restore it change.
1.5) Tree Node Initialization
# Python TreeNode Class
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Create a simple tree
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
// Java TreeNode Class
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
2) Problems by Pattern Classification
2.1) Problem Categories & Templates Priority 4 of 5 — High value — a gap here costs you rounds
Tree Traversal Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Binary Tree Preorder Traversal | 144 | DFS Preorder | Preorder Template | Easy |
| Binary Tree Inorder Traversal | 94 | DFS Inorder | Inorder Template | Easy |
| Binary Tree Postorder Traversal | 145 | DFS Postorder | Postorder Template | Easy |
| Binary Tree Level Order Traversal | 102 | BFS Level-order | BFS Template | Medium |
| Binary Tree Zigzag Level Order | 103 | BFS with alternating | BFS + Direction | Medium |
Tree Property Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Maximum Depth of Binary Tree | 104 | DFS Bottom-up | Postorder Height | Easy |
| Minimum Depth of Binary Tree | 111 | BFS/DFS | BFS Early Stop | Easy |
| Balanced Binary Tree | 110 | DFS Height Check | Height Validation | Easy |
| Symmetric Tree | 101 | DFS Comparison | Mirror Validation | Easy |
| Same Tree | 100 | DFS Comparison | Tree Comparison | Easy |
Path-Based Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Binary Tree Maximum Path Sum | 124 | DFS Path Tracking | Global Max Update | Hard |
| Path Sum | 112 | DFS Path Validation | Path Accumulation | Easy |
| Path Sum II | 113 | DFS Path Collection | Path + Backtrack | Medium |
| Path Sum III | 437 | DFS Prefix Sum | Path Count Tracking | Medium |
| Sum Root to Leaf Numbers | 129 | DFS Path Calculation | Path Value Building | Medium |
| Count Good Nodes in Binary Tree | 1448 | DFS Path Max | Path State Tracking | Medium |
| Diameter of Binary Tree | 543 | DFS Path Length | Longest Path | Easy |
| Longest Univalue Path | 687 | DFS Path Pattern | Same Value Path | Medium |
Distance and LCA Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Lowest Common Ancestor | 236 | DFS Post-order | LCA Standard | Medium |
| LCA of BST | 235 | BST Property | Value Comparison | Easy |
| Distance in Binary Tree | 1740 | LCA + Distance | Path Distance | Medium |
| All Nodes Distance K | 863 | Graph + BFS | Tree to Graph | Medium |
| Smallest Subtree w/ Deepest Nodes | 865/1123 | LCA + Depth Comparison | Result(node, dist) DFS | Medium |
Height and Depth Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Maximum Depth | 104 | DFS Bottom-up | Height Calculation | Easy |
| Minimum Depth | 111 | BFS/DFS | Depth to Leaf | Easy |
| Balanced Binary Tree | 110 | DFS Height Validation | Balance Check | Easy |
| Find Bottom Left Tree Value | 513 | BFS Level-order | Leftmost at Depth | Medium |
Tree Construction Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Construct Binary Tree from Preorder and Inorder | 105 | Divide & Conquer | Tree Building | Medium |
| Construct Binary Tree from Inorder and Postorder | 106 | Divide & Conquer | Tree Building | Medium |
| Serialize and Deserialize Binary Tree | 297 | Tree Encoding | String Conversion | Hard |
| Construct String from Binary Tree | 606 | DFS String Building | String Construction | Easy |
Tree Modification Problems
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Invert Binary Tree | 226 | DFS Node Swapping | Tree Inversion | Easy |
| Flatten Binary Tree to Linked List | 114 | DFS Restructuring | Tree Flattening | Medium |
| Merge Two Binary Trees | 617 | DFS Combination | Tree Merging | Easy |
| Delete Nodes And Return Forest | 1110 | DFS + State Tracking | Tree Deletion + Forest Formation | Medium |
Subtree Comparison Problems (Node Path Pattern)
| Problem | LC # | Pattern | Template | Difficulty |
|---|---|---|---|---|
| Find Duplicate Subtrees | 652 | Node Path Serialization | Subtree Hashing | Medium |
| Subtree of Another Tree | 572 | Node Path Comparison | Subtree Matching | Easy |
| Count Univalue Subtrees | 250 | Node Path Validation | Subtree Property Check | Medium |
2.2) Pattern Selection Guide
Problem Analysis Decision Tree:
1. Need to process all nodes?
├── Yes: Choose appropriate traversal (preorder/inorder/postorder/level-order)
└── No: Continue
2. Need information from children for parent?
├── Yes: Use POSTORDER traversal
└── No: Continue
3. Need information from parent for children?
├── Yes: Use PREORDER traversal
└── No: Continue
4. Processing level by level?
├── Yes: Use BFS/Level-order traversal
└── No: Continue
5. Need to move upward (to parent) or explore multi-directionally?
├── Yes: Use MOVE PARENT pattern (Build parent map + BFS)
└── No: Continue
6. Need to compare or find duplicate subtrees?
├── Yes: Use NODE PATH pattern (Subtree serialization)
└── No: Continue
7. Working with BST and need sorted order?
├── Yes: Use INORDER traversal
└── No: Use any suitable approach
3) Summary & Quick Reference
3.1) Tree Algorithm Complexity Summary
| Operation | Balanced Tree | Unbalanced Tree | Space Complexity |
|---|---|---|---|
| Search | O(log n) | O(n) | O(h) recursion |
| Insert | O(log n) | O(n) | O(h) recursion |
| Delete | O(log n) | O(n) | O(h) recursion |
| Traversal | O(n) | O(n) | O(h) recursion |
| Height Calculation | O(n) | O(n) | O(h) recursion |
3.2) Traversal Quick Reference
| Traversal | Order | Use Case | Key Characteristics |
|---|---|---|---|
| Preorder | Root → Left → Right | Tree copying, serialization | Process parent before children |
| Inorder | Left → Root → Right | BST sorted output | Process left, then root, then right |
| Postorder | Left → Right → Root | Tree deletion, calculations | Process children before parent |
| Level-order | Level by level | Tree printing, shortest path | Use queue, process by levels |
3.3) Problem-Solving Templates
Path Tracking Template
def solve_path_problem(root, target):
def dfs(node, current_path, current_sum):
if not node:
return
# Add current node to path
current_path.append(node.val)
current_sum += node.val
# Check if we found target
if not node.left and not node.right: # Leaf node
if current_sum == target:
result.append(current_path[:]) # Add copy of path
# Recurse to children
dfs(node.left, current_path, current_sum)
dfs(node.right, current_path, current_sum)
# Backtrack
current_path.pop()
result = []
dfs(root, [], 0)
return result
3.4) Common Patterns & Tricks
Height vs Depth Pattern
# Height (bottom-up, postorder)
def height(node):
if not node:
return 0
return 1 + max(height(node.left), height(node.right))
# Depth (top-down, preorder)
def calculate_depth(node, depth=0):
if not node:
return
node.depth = depth # Assign depth to node
calculate_depth(node.left, depth + 1)
calculate_depth(node.right, depth + 1)
Global Variable Pattern
class Solution:
def __init__(self):
self.max_sum = float('-inf') # Global result
def max_path_sum(self, root):
def dfs(node):
if not node:
return 0
left_max = max(0, dfs(node.left)) # Ignore negative paths
right_max = max(0, dfs(node.right))
# Update global maximum
self.max_sum = max(self.max_sum, node.val + left_max + right_max)
# Return maximum path through this node
return node.val + max(left_max, right_max)
dfs(root)
return self.max_sum
3.5) Common Mistakes & Tips Priority 4 of 5 — High value — a gap here costs you rounds
🚫 Common Mistakes:
- Forgetting base cases in recursion
- Modifying tree structure incorrectly during traversal
- Not handling null nodes properly
- Using wrong traversal order for the problem
- Stack overflow due to deep recursion (use iterative approach)
✅ Best Practices:
- Always check for null nodes first
- Use helper functions to pass additional parameters
- Consider iterative solutions for very deep trees
- Validate input and handle edge cases
- Use meaningful variable names (left_result, right_result)
- Test with balanced and unbalanced trees
3.6) Interview Tips
- Clarify the Problem: Ask about null inputs, tree structure, expected output format
- Start with Recursive Solution: Most tree problems have elegant recursive solutions
- Consider Iterative Alternative: If recursion depth might be an issue
- Trace Through Examples: Walk through small examples to verify logic
- Analyze Complexity: Always discuss time and space complexity
- Handle Edge Cases: Empty tree, single node, very deep trees
3.7) Related Topics
- Binary Search Trees: Ordering property enables efficient operations
- Heaps: Complete binary trees with heap property
- Tries: Prefix trees for string operations
- Segment Trees: For range query problems
- Graph Algorithms: Trees are special cases of graphs
Advanced Tree Techniques — Binary Lifting, Re-rooting, Morris Traversal
Binary Lifting — LCA in O(log n) per Query Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
import math
def build_binary_lifting(root, n):
"""Preprocess tree for O(log n) LCA queries."""
LOG = max(1, int(math.log2(n)) + 1)
parent = [[-1] * n for _ in range(LOG)]
depth = [0] * n
# BFS to set parent[0] and depth
from collections import deque
queue = deque([root.val])
visited = {root.val}
# ... (wire up parent[0][v] = direct parent of v)
# Fill sparse table: parent[k][v] = 2^k-th ancestor of v
for k in range(1, LOG):
for v in range(n):
if parent[k-1][v] != -1:
parent[k][v] = parent[k-1][parent[k-1][v]]
return parent, depth
def lca(u, v, parent, depth, LOG):
# Bring u and v to the same depth
if depth[u] < depth[v]: u, v = v, u
diff = depth[u] - depth[v]
for k in range(LOG):
if (diff >> k) & 1:
u = parent[k][u]
if u == v: return u
# Move both up until they meet
for k in range(LOG - 1, -1, -1):
if parent[k][u] != parent[k][v]:
u = parent[k][u]; v = parent[k][v]
return parent[0][u]
Time: O(n log n) preprocessing, O(log n) per LCA query. Use for: LC 236 (LCA) when query count is large, path sum between any two nodes.
Re-rooting DP — Answer for Every Root
# LC 310 Minimum Height Trees — find roots minimizing tree height
# Equivalent: find centroid(s) of tree
def findMinHeightTrees(n, edges):
if n == 1: return [0]
from collections import defaultdict, deque
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v); graph[v].add(u)
leaves = deque(i for i in range(n) if len(graph[i]) == 1)
remaining = n
while remaining > 2:
remaining -= len(leaves)
new_leaves = deque()
while leaves:
leaf = leaves.popleft()
neighbor = next(iter(graph[leaf]))
graph[neighbor].remove(leaf)
if len(graph[neighbor]) == 1:
new_leaves.append(neighbor)
leaves = new_leaves
return list(leaves)
Tree Serialization / Deserialization — LC 297
See tree_codec.md for the pre-order + null-marker codec (Python and Java), the parenthesis format (LC 606 / 536) and the depth-prefix format (LC 1028).
Morris Traversal (O(1) Space) — Compact Reference
See the full In-order Morris template (Python + Java) in Template 5: Morris Traversal above. Key idea: thread each node’s empty right pointer to its in-order successor, then unthread on the second visit — O(n) time, O(1) space. Pre/post-order variants follow the same threading.
Interview tips — trees
| Signal | Pattern |
|---|---|
| “diameter / longest path” | Post-order: return height, track max diameter |
| “lowest common ancestor” | Recursive: if root is one of nodes, return root |
| “LCA with many queries” | Binary lifting (sparse table) |
| “answer for each node as root” | Re-rooting DP (2 DFS) |
| “serialize/deserialize tree” | Pre-order DFS with null markers |
| “validate BST” | In-order should be strictly increasing |
| “balanced BST from sorted array” | Mid-point recursion |
| “O(1) space traversal” | Morris threading |
| “path sum between any two nodes” | Post-order: track max path through each node |