Trees
Trees are hierarchical structures. Many problems are naturally recursive and revolve around how information flows through the tree.
1. Traversals (The "Vocabulary")
Before solving tree problems, you must be comfortable with the standard traversals. Each visits nodes in a different order:
- Preorder (DFS): Root -> Left -> Right. Used for copying trees or prefix expressions.
- Inorder (DFS): Left -> Root -> Right. Crucial for BSTs (yields sorted order).
- Postorder (DFS): Left -> Right -> Root. Used for deleting trees or aggregating info from children (e.g., height).
- Level-Order (BFS): Row by row. Used for finding shortest paths in unweighted graphs.
2. Thinking Recursively: The Information Flow
Most tree problems boil down to one question: What information needs to flow where?
- Down the tree (Parameters): Pass values from parent to child.
- Examples:
current_depth,max_value_so_far,target_sum_remaining.
- Examples:
- Up the tree (Return Values): Pass values from child to parent.
- Examples:
subtree_height,is_balanced,max_path_sum_in_subtree.
- Examples:
- Global State: Update a variable outside the recursion.
- Examples:
max_diameter,result_list.
- Examples:
3. Tree Properties & Types
Not all binary trees are created equal. The structure determines the runtime.
- Perfect: Every level is completely filled. Count = .
- Complete: Filled except possibly the last level (nodes aligned left). Heaps are complete trees.
- Balanced: Height is . Operations are fast.
- Skewed: Height is . Operations degrade to linked list performance.
4. Common Techniques & Recipes
The "Node-Depth Queue" (Iterative BFS)
When solving level-order problems iteratively, store pairs of (node, depth) in your queue.
queue = deque([(root, 0)])
while queue:
node, depth = queue.popleft()
# process node at depth
if node.left: queue.append((node.left, depth + 1))
if node.right: queue.append((node.right, depth + 1))
The "Parallel Pointers" Strategy For problems involving two trees (e.g., merging, comparing), iterate through both simultaneously.
- If both are BSTs, you can use iterative inorder traversals to treat them like sorted arrays (see Merge Two BSTs).
- If checking for equality, traverse both structure-wise in lockstep.
Practice Problems
Maximum Depth of Binary Tree
LeetCode: https://leetcode.com/problems/maximum-depth-of-binary-tree/
Invert Binary Tree
LeetCode: https://leetcode.com/problems/invert-binary-tree/
Validate Binary Search Tree
LeetCode: https://leetcode.com/problems/validate-binary-search-tree/
Convert BST to Greater Tree
LeetCode: https://leetcode.com/problems/convert-bst-to-greater-tree/
Kth Smallest in BST
LeetCode: https://leetcode.com/problems/kth-smallest-element-in-a-bst/
Merge Two BSTs into Array
LeetCode: https://leetcode.com/problems/all-elements-in-two-binary-search-trees/
Closest Value in BST
LeetCode: https://leetcode.com/problems/closest-binary-search-tree-value/
Beyond Cracking the Coding Interview
Tree Size
Return the number of nodes in a binary tree.
Lowest Common Ancestor (With Parent Pointers)
Given two nodes with parent pointers, find their lowest common ancestor.
Aligned Path
Given a binary tree, a node is aligned if its value equals its depth. Return the longest path consisting only of aligned nodes.
Triangle Rule
Count triples (a, b, c) where a is the LCA of b and c, b and c are at the same depth, and the path to b uses only left edges while the path to c uses only right edges.
Evaluate N-ary Expression Tree
Evaluate an N-ary tree where internal nodes are operations (sum, product, min, max) and leaves are numbers.
Most Prolific Level
Find the level whose next level has the most nodes. If there is no next level, treat prolificness as 0.