Algorithm

Lowest Common Ancestor

Trees Pattern

Lowest Common Ancestor

Given the root of a binary tree and two nodes p and q that are both present in it, return their lowest common ancestor: the deepest node that has both p and q somewhere in its subtree. A node counts as a descendant of itself, so if p happens to be an ancestor of q, the answer is p. Return the node itself, not its value. This is an ordinary binary tree — no search-tree ordering may be assumed.

CONSTRAINTS
  • The number of nodes in the tree is in the range [2, 10⁵]
  • -10⁹ <= Node.val <= 10⁹, and all values are unique
  • p and q are distinct and both exist in the tree
  • A node is considered a descendant of itself
EXAMPLE 1
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
5 sits in the root's left subtree and 1 in its right, so the root is the only node containing both. Any node below the root is missing at least one of them.
EXAMPLE 2
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
Output: 5
4 lies beneath 5, and because a node counts as its own descendant, 5 itself qualifies as an ancestor of both. Answering 3 would be a common ancestor, just not the lowest one.
EXAMPLE 3
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 7, q = 4
Output: 2
7 and 4 are the two children of 2, so their paths from the root diverge exactly there. Everything above 2 also contains both, but sits higher.
EXAMPLE 4
Input: root = [1,2], p = 1, q = 2
Output: 1
The root is one of the two targets and contains the other, so it is the answer — the same self-ancestor rule, at the top of the tree.
Can a node be its own ancestor?
Yes, and this rule decides several cases. If p is an ancestor of q, the answer is p rather than p's parent.
Is this a binary search tree?
No, so you cannot compare values to choose a direction. If it were a BST, the ordering would let you walk down from the root without any recursion at all — a much cheaper variant worth mentioning.
Are p and q guaranteed to exist?
Yes here. That guarantee is load-bearing: the standard solution returns a node even when only one target is present, so a version without the guarantee would need a second pass or a count to verify both were actually found.
Should I compare nodes by value or by reference?
By reference, since you are given the node objects. Values are unique here so either works, but with duplicates only reference comparison is well defined.

Two nodes, each reachable from the root by exactly one downward route. Follow both routes from the top and they coincide for a while, then split. The last node they share is the lowest common ancestor — everything above it is also common but higher, and after the split neither branch contains both.

That description suggests an obvious method, and it is worth doing first because it is easy to get right: find the root-to-node path for p and for q — the technique from Root to Node Path — then walk the two lists side by side and take the last position where they agree. Correct, two traversals, and O(H) memory for the two lists. In an interview it is a perfectly respectable first answer.

But it stores whole paths in order to use only their divergence point. Can a single walk find that point directly?

Let the answer be the return value

Here is the reframe. In Root to Node Path each node returned a boolean, "is the target below me?", and the real output was kept in a shared list. Make the return value richer: instead of whether something was found, return what was found — a node, or null for nothing.

Define the recursion as: return a node from this subtree that is relevant to the answer, or null if this subtree is irrelevant. Then look at what a node sees when both its calls come back:

- Both sides non-null. The left subtree contained one target and the right subtree the other. This node is where the two routes part, so this node is the answer. Return itself.
- Exactly one side non-null. Everything relevant lies in that one subtree. This node cannot be the split point, so it has no business claiming the answer — it just forwards whatever that side reported.
- Both null. Nothing relevant down here at all. Report null.

And the base case does double duty. A null node reports null, obviously. But a node that is p or q reports itself immediately, without even looking below it.

python
def lca(node, p, q):
    if node is None or node is p or node is q:
        return node                    # null, or "I am a target" — either way, report it
    left  = lca(node.left,  p, q)
    right = lca(node.right, p, q)
    if left and right:
        return node                    # the two targets split here: I am the LCA
    return left or right               # otherwise forward whichever side found something

Four lines, one traversal, no path lists. The work happens after both calls return, so this is a post-order shape — unavoidably, since a node cannot know whether it is the split point until both sides have reported.

Why stopping at a target is not cheating

The line that deserves scrutiny is the early return at node is p: we stop and report without checking whether q is somewhere below. Does that risk missing a lower ancestor?

No, and the reason is the self-ancestor rule. Suppose q does lie beneath p. Then every common ancestor of p and q is p or one of p's ancestors, and the lowest of those is p itself — exactly what we returned. Suppose q does not lie beneath p. Then p's subtree contains only one target, and reporting p upward simply means "the p-side found something"; some ancestor will eventually pair it with the report from q's side and correctly claim itself. Either way the early return is right, and it saves exploring p's subtree entirely.

Crucial Notethe "both sides non-null" test is what makes a node claim the answer, and it is only sound because the problem guarantees both p and q exist. If one of them were absent, a subtree containing just the other would still report a non-null node, and the algorithm would happily return an ancestor of a target that has no partner — an answer to a question nobody asked. When an interviewer removes that guarantee, the fix is to stop returning a bare node and instead return counts, or run a separate existence check first. Notice how much the guarantee is doing: it is the reason four lines suffice.
Worked example:root 3, children 5 and 1; 5's children are 6 and 2; 2's children are 7 and 4. Targets p = 5, q = 4
- At 3: not a target, so recurse both ways.
- Down the left: at 5 — this is p, so return 5 straight away. Nodes 6, 2, 7 and 4 are never visited on this path.
- Down the right: at 1, not a target; both its children (0 and 8) return null, so 1 returns null.
- Back at 3: left = 5, right = null. Only one side is non-null, so 3 forwards 5.
- Answer: 5 — correct, since 4 sits beneath 5 and a node is its own ancestor.

Now the same tree with p = 7, q = 4. Node 2 receives 7 from its left and 4 from its right; both non-null, so 2 returns itself. Node 5 gets 2 from the left and null from the right, so it forwards 2. Node 3 gets 2 from the left and null from the right, and forwards it again. Answer: 2. Once a node claims the answer, every ancestor above it is in the one-sided case and simply passes it along untouched — which is exactly why the "lowest" ancestor wins rather than some higher one.

Cost, and what to take away

One visit per node in the worst case, constant work each: O(N) time, down from the two passes of the path-comparison method (still O(N), but twice the work and O(H) extra storage). Space here is just the recursion stack, O(H).

The move worth stealing is the upgrade from a boolean return to an informative one. When a recursion is about to answer "did I find it?", ask whether returning the thing itself — or the best candidate from this subtree — would let the parent decide something it otherwise could not. Here, "return a relevant node" let each parent distinguish the split point from a pass-through with a single null check, and that one decision is the entire algorithm. The same upgrade pays off in All Nodes Distance K, where the recursion returns a distance rather than a flag, and in Binary Tree Maximum Path Sum, where it returns the best downward sum.

Interactive Strategy Visualization

Common Ancestry Audit

Bottom-Up Recursive Returns

32851
Starting search for Target A(5) and B(1). Starting DFS from Root.
Recursive Trace
Stage
Explore Root (3)
Discovery Condition

If `left` AND `right` subtrees return values, the current node is the LCA.

"Nodes report back if they found either p or q."

Strategy

Post-order traversal: A node is the LCA if it finds targets in both subtrees or if it is one of the targets and finds the other in its subtrees.

"Returns p, q, or LCA up the chain."

O(N) Two Passes · O(H) Space Compare Root Paths
O(N) Time · O(H) Space Single Post-order Walk