Given the root of a binary tree and two node values p and q (both guaranteed to exist, all node values are unique), return the value of their lowest common ancestor (LCA) — the deepest node that has both p and q as descendants (a node is a descendant of itself).
The tree is given in level-order (breadth-first), using null for missing children.
Input: root = [3, 5, 1, 6, 2, 0, 8], p = 5, q = 1 Output: 3 5 and 1 sit in different subtrees of 3, which is their lowest common ancestor.
Input: root = [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4], p = 5, q = 4 Output: 5 4 is a descendant of 5, and a node is a descendant of itself, so the LCA is 5.
- The number of nodes is in the range [2, 10^5] - -10^9 <= Node.val <= 10^9 - All Node.val are unique - p != q is not required; both p and q exist in the tree
The lowest common ancestor is the deepest node from which p and q sit in different subtrees (or which is itself one of them). A single postorder recursion finds it: return a node if it is p or q; otherwise, if p and q surface from both children, this node is the LCA; if they surface from only one side, pass that result up.
“Are values unique?”
Yes — each node has a distinct value, so a value identifies a node.
“Are p and q guaranteed present?”
Yes, both exist in the tree.
I recurse and return a node if it matches p or q; otherwise I combine the results from both children.
If both children report a find, the current node is the split point — the LCA; if only one does, I pass it upward.
Worked example — tree [3, 5, 1, 6, 2, 0, 8], p = 5, q = 1
3
/ \
5 1
/ \ / \
6 2 0 8
dfs(5) returns 5 (matches p) ; dfs(1) returns 1 (matches q)
at node 3: L=5, R=1 both non-null -> 3 is the LCA
answer: 3
Returning the node as soon as it equals p or q lets an ancestor detect 'this side contains a target'.
If both children return something, the targets diverge here, so this node is the LCA.
When only one child returns a node, that's either the LCA found deeper or a lone target being carried toward its partner.
| Root-to-node paths | Single postorder recursion | |
|---|---|---|
| Idea | Find both paths, take last shared node | Return matches; both sides non-null = LCA |
| Time | O(n) | O(n) |
| Space | O(n) | O(height) |
Both are O(n); the recursion needs no explicit paths. Full code is in the Approaches selector below.
Key takeaway
Recurse: return a node if it is p or q; if both children return non-null, this node is the LCA; otherwise bubble up the single non-null result. O(n) time, O(height) space.
dfs(node):
if node is null or node.val in {p, q}: return node
L = dfs(left); R = dfs(right)
if L and R: return node
return L or R