Last Updated: March 29, 2026
We have a binary tree (not a BST, just a regular binary tree with no ordering guarantees) and two nodes, p and q, that are guaranteed to exist somewhere in it. We need to find their lowest common ancestor, the deepest node that has both p and q in its subtree (including itself).
What makes this different from the BST version (LeetCode #235) is that we can't use value comparisons to navigate. In a BST, we know exactly which direction to search based on node values. Here, p and q could be anywhere. A node with value 5 might be in the right subtree of a node with value 8. There's no ordering to exploit.
So the key question becomes: how do we figure out where p and q are relative to any given node? The answer is recursive exploration. For each node, we check whether p or q exists in its left subtree, its right subtree, or if the node itself is one of them. The first node we encounter (going bottom-up) that has p on one side and q on the other (or IS one of them with the other below it) is our LCA.
[2, 10^5] nodes -> We need O(n) or better. Anything that visits each node a constant number of times is fine.-10^9 <= Node.val <= 10^9 -> Large values, but we only compare node references (not values) in the core algorithm, so no overflow concerns.p and q exist in the tree -> We don't need to handle "not found" cases. The LCA is guaranteed to exist.The most natural way to think about this problem is with recursion. Picture yourself standing at any node in the tree. You want to know: does p live somewhere below me on the left? Does q live somewhere below me on the right? Or am I one of them?
Here's the key insight: if we do a DFS and propagate "found" signals upward, the LCA is the first node where signals arrive from both sides. Concretely, we write a recursive function that returns a node if it finds p or q in the subtree, or null if neither is there. Then:
p is on one side and q is on the other.p and q are in that subtree, so we pass the result upward.p or q, we return it immediately. The other node must be somewhere below (guaranteed by the problem), so this node is the LCA.That last point is subtle but important. When we find p, we return it without searching further down. Why? Because if q is below p, then p is the LCA. And if q is in a completely different branch, the recursion will find it there and the merging logic at a higher ancestor will handle it.
p or q, return the current node.The recursive approach is elegant and efficient, but it uses the call stack implicitly. What if we separated the two concerns: first build a way to trace paths, then find where the paths diverge?
Here's a completely different way to think about the problem. If you had a way to walk upward from any node to the root (like following parent pointers), finding the LCA is straightforward: trace the path from p to the root, trace the path from q to the root, and find the deepest node that appears in both paths.
The tree doesn't give us parent pointers by default, but we can build them ourselves. A single BFS from the root lets us record each node's parent in a hash map. Once we have that, we trace p's ancestors into a set, then walk q upward until we hit a node that's already in that set. That node is the LCA.
This approach separates the problem into two clean steps: (1) build parent pointers, (2) find the intersection of ancestor paths. It's more intuitive to reason about and avoids recursion entirely, which means no risk of stack overflow.
Think of the ancestor path from any node to the root as a chain. The LCA is the deepest point where the chains from p and q merge into a single path. By collecting all of p's ancestors into a set and then walking q upward, we're guaranteed to hit this merge point. The first ancestor of q that also appears in p's chain is, by definition, the lowest common ancestor.
p, walk upward using the parent map, adding each ancestor to a set (including p itself).q, walk upward using the parent map. The first node that already exists in the ancestor set is the LCA.