Lowest Common Ancestor of a Binary Tree
WHAT IT SAYS
Given a binary tree and two nodes p and q, find the deepest node that has both of them in its subtree.
WHAT IT'S REALLY ASKING
"Walk down from the root toward both targets at once. For a while they pull you the same way — then comes a node where they split into different children, and every step past it abandons one of them. The LCA is not something you search FOR; it is the point where the two searches part company."
Record both root-to-target paths, then compare
O(n) time, O(h) space for two stored paths, up to three traversalsFind the path from the root to p and store it. Find the path from the root to q and store it. Walk the two lists in parallel from the front; the last node where they still agree is the answer.
WHERE THE WORK IS WASTED — The two path-finding searches walk the same tree and, near the root, the same nodes — the shared prefix of the two paths is traversed twice and then compared a third time, element by element, to rediscover that it is shared. But the sharing was knowable DURING a single walk: at every node, a subtree either contains p, contains q, contains both, or contains neither, and one traversal can learn which. Materialising the paths defers to a post-processing step a question the recursion could have answered on the way up.
The LCA is where the two reports first meet.
Design the recursion around one question, asked of every subtree: what do you contain? Give each call this contract — searching a subtree returns p if it contains p (and not q), q if it contains q (and not p), the LCA if it contains both, and null if it contains neither. If that contract holds for the children, watch what the parent can conclude. Ask both children. If both come back non-null, then p is in one subtree and q is in the other — they are in DIFFERENT children, so this node is the deepest point containing both. This node is the LCA, and the contract says: return it. Every ancestor above will see it come up from one side and null from the other, and by the contract's other clause will pass it along unchanged — which is exactly right, because the answer is already found. If exactly one child reports non-null, everything interesting lives on that side; forward its report upward untouched. If both report null, report null. One clause remains: what if the current node IS p or q? Return it immediately, without even looking below — and this is not laziness but correctness. If the other target is somewhere underneath, then this node is an ancestor of both, hence the LCA, and returning it is the right answer. If the other target is elsewhere, some ancestor will see this report on one side and the other target's report on the other side, and that ancestor will correctly crown itself. Either way, returning self is safe — the case analysis closes. The elegance worth internalising: the function never explicitly computes 'the LCA'. It only ever reports what it found, and the LCA emerges as the unique node where two non-null reports collide. The answer is a property of how information flows upward, not of any search step.
One post-order walk with the found-below contract
O(n) time — every node asked once — O(h) stack spaceIf the node is null or equals p or q, return it. Recurse into both children. If both returns are non-null, return the current node. Otherwise return whichever child's report is non-null (or null). The value that surfaces at the root is the LCA.
Reports Collide at the Answer
YOU'LL SEE IT AGAIN WHEN
- The answer is defined relative to two (or more) marked elements — their meeting point, split point, or shared ancestor.
- Each subtree's relationship to the marked elements is summarisable in a small enum: neither, one, the other, both.
- The answer node is characterised by a condition on its children's summaries (both non-null), so it identifies itself during the upward pass.