Problem
Given a binary search tree and two nodes p and q, find the deepest node that is an ancestor of both. A node counts as its own ancestor, so if q is a descendant of p, the answer is p itself.
[6,2,8,0,4,7,9,null,null,3,5]
- Input: root = above BST, p = 2, q = 4
- Output: 2
- Explanation: Node 4 lives in node 2's right subtree, making 2 the lowest node that contains both.
Counter-example: if p = 2 and q = 8, the answer is 6 โ not 2 โ because 8 is entirely in the right subtree and the two targets diverge at the root.
Intuition
A BST's ordering property tells you exactly where the LCA must be at every step: if both targets are smaller than the current node, the LCA lives in the left subtree; if both are larger, it lives in the right subtree. The instant the two targets land on opposite sides of a node โ or the current node matches one of them โ you've reached the split point, which is the LCA.
Solution โ Iterative BST Traversal
Walk down the tree one step at a time. At each node, compare both target values to the current value to decide which direction to go. Return the first node where the targets no longer agree on a direction.
- Start at the root.
- If both
p.valandq.valare strictly less thannode.val, move to the left child โ the LCA must be smaller. - If both are strictly greater than
node.val, move to the right child โ the LCA must be larger. - Otherwise โ the targets straddle the current node, or one of them equals it โ return the current node.
1def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
2 node = root
3 while node:
4 if p.val < node.val and q.val < node.val:
5 node = node.left # both targets are smaller โ LCA is to the left
6 elif p.val > node.val and q.val > node.val:
7 node = node.right # both targets are larger โ LCA is to the right
8 else:
9 return node # split point or exact match โ this is the LCA- Time: O(h) โ one step taken per iteration, where h is the tree height; O(log n) for a balanced BST, O(n) worst case for a degenerate skewed tree.
- Space: O(1) โ only a single pointer is maintained, no recursion stack.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Iterative BST Traversal | O(h) | O(1) | Any BST LCA โ exploits ordering for O(1) space vs the O(h) recursive alternative |
Common Mistakes
- Applying this approach to a general binary tree: BST value comparisons only work because of the BST ordering guarantee. On an arbitrary binary tree you must use the general O(n) post-order DFS approach instead โ this one silently produces wrong answers without that guarantee.
- Using
<=or>=in the directional checks: The conditions must be strict (<and>). Whenp.val == node.val, neither branch should fire โ the current node is the answer and theelsemust catch it. - Assuming the smaller target is always the LCA: The LCA is the split-point, not the smaller value. For p = 0 and q = 4, the LCA is 2, not 0.
- Writing the recursive version and overlooking its stack cost: A recursive implementation of the same comparisons uses O(h) space due to the call stack. The iterative version shown here is strictly better in space.
- Forgetting that a node is its own ancestor: If p = 4 and q = 3 (where 3 is in 4's left subtree), the answer is 4. The
elsebranch handles this because whennode.val == p.val, neither strict-less nor strict-greater fires.
Related Problems
lowest-common-ancestor-of-a-binary-treeโ same goal on a general binary tree where you cannot use value comparisons and must visit every nodesearch-in-a-binary-search-treeโ the same left/right navigation pattern, just searching for a single targetkth-smallest-element-in-a-bstโ BST traversal using in-order ordering propertiesdelete-node-in-a-bstโ BST traversal that locates a target node using the same comparison logicbinary-searchโ the same divide-and-eliminate-half idea applied to a sorted array instead of a BST