Master the divide-and-conquer pattern to locate the lowest common ancestor node shared by two target nodes in a tree.
Learn how dynamic arrays achieve O(1) amortized insertion, contiguous memory layout, and geometric doubling strategies.
The Lowest Common Ancestor (LCA) of two nodes $p$ and $q$ in a tree is the deepest node that has both $p$ and $q$ as descendants (where a node can be a descendant of itself).
3 (LCA of 5 and 1)
/ \
[5] [1]
/ \ / \
6 2 0 8
/ \
7 4
For any subtree rooted at root:
root is null, p, or q, return root.root is the split point and therefore the LCA!function lowestCommonAncestor(root, p, q) {
// Base case: Reached null or found p or q
if (!root || root === p || root === q) {
return root;
}
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
// If p and q are found in different subtrees, root is the LCA!
if (left !== null && right !== null) {
return root;
}
// Otherwise, return whichever subtree found a target
return left !== null ? left : right;
}
On a BST, we can use the ordering invariant to find LCA in $O(H)$ time without full tree exploration:
function lowestCommonAncestorBST(root, p, q) {
while (root) {
if (p.val < root.val && q.val < root.val) {
root = root.left; // Both targets in left subtree
} else if (p.val > root.val && q.val > root.val) {
root = root.right; // Both targets in right subtree
} else {
return root; // Split point found!
}
}
return null;
}
Use divide-and-conquer on tree subproblems. Next, let's explore Heaps and Priority Queues!