Lowest Common Ancestor Binary Tree: Three Approaches

Lowest Common Ancestor Binary Tree: Three Approaches

Three approaches to the lowest common ancestor binary tree problem with step by step walkthroughs, from recursive postorder to binary lifting.

10 minutes
Intermediate
What you will learn

What the lowest common ancestor is and why interviews test it

How the recursive postorder approach finds LCA in `O(n)`

How LCA II handles cases where nodes might not exist

How binary lifting answers repeated LCA queries in `O(log n)`

You're given two nodes in a binary tree. Find their lowest common ancestor. The lowest common ancestor binary tree problem shows up constantly across Google, Amazon, and Meta interviews, and the common sticking point isn't the concept itself, but the recursive invariant that makes the solution clean.

TL;DR
The lowest common ancestor of two nodes is the deepest node that has both as descendants. A recursive postorder solution finds it in O(n). Binary lifting handles repeated queries in O(log n) each after O(n log n) preprocessing.

What lowest common ancestor means in a binary tree

The lowest common ancestor of two nodes p and q in a binary tree is the deepest node that has both p and q as descendants. A node counts as its own descendant. A recursive postorder solution finds it in O(n) time by bubbling results up from the leaves.

That definition packs in more than you'd expect.

Deepest: What separates the lowest common ancestor from any common ancestor. The root is always a common ancestor of every pair of nodes in the tree. But the LCA is the one closest to p and q, sitting exactly where their paths from the root first diverge.

Why does this show up so often in interviews? The lowest common ancestor binary tree problem is a building block for three other problems: distance between two nodes, diameter computation through a specific pair, and range queries on tree structures. It also tests whether you can think in postorder traversal, passing information upward from children to parent, without the overhead of dynamic programming.

Outside interviews, LCA queries appear in version control systems (finding the common commit ancestor of two branches), network routing (finding the nearest shared router), and taxonomy classification. Git's merge base command, for example, is an LCA query on the commit DAG.

Finding lowest common ancestor algorithm

The most common interview solution uses a single postorder traversal. The invariant fits in one sentence. If p and q are in different subtrees of a node, that node is the LCA.

Here's the full logic: Recurse into both children first. When the left subtree contains one target and the right subtree contains the other, the current node is their lowest common ancestor. If both targets land in the same subtree, pass that result up. And if a node is one of the targets, return it immediately. The parent will figure out whether the other target is on the opposite side.

  1. Python

Walk through this on a concrete tree. Nodes 1 through 7, where 1 is the root. Node 2 and 3 are children of 1. Nodes 4 and 5 are children of 2. Nodes 6 and 7 are children of 3. Find the LCA of nodes 5 and 6.

The function recurses to the leaves first. At node 4, neither target matches, both children return null, so it returns null. At node 5, it matches p and returns itself. Node 2 receives null from the left (node 4) and node 5 from the right. Only one side returned a value, so node 2 passes node 5 upward.

On the right side, node 6 matches q and returns itself. Node 7 returns null. Node 3 gets node 6 from the left and null from the right, so it passes node 6 upward.

Now at the root, both sides returned non-null values. Left gave node 5, right gave node 6. The two targets split across the subtrees. Node 1 is the LCA.

The time complexity is O(n) because every node is visited at most once. Space complexity is O(h) for the recursion stack, where h is the tree's height.

Interviewers reach for this problem because it tests postorder reasoning, moving state from children to parent, without needing DP transitions or explicit memoisation. It's recursive state management at its most stripped down. If you can derive this invariant from scratch, you probably understand recursive tree algorithms well enough to handle the harder variants.

“The LCA invariant is one sentence: if p and q are in different subtrees, you're standing on the answer.”
Recursive state propagation

Edge cases that break clean implementations

The three line core looks simple, but there are specific edge cases that trip people up during interviews.

  • Target as ancestor: If p is a direct ancestor of q, the function returns p the moment it hits that node. It never reaches q. This is correct behavior because p is the LCA in this case, but candidates often second-guess the early return and add unnecessary tracking. Trust the invariant. If p is the ancestor of q, the recursion into p's subtree will find q, but the call at p already returned p itself. The parent receives exactly one non-null child, so it passes p upward. No special case needed.
  • Identical targets: When p == q, the function returns that node immediately. You won't encounter this in standard interview problems (they guarantee distinct nodes), but utility functions that wrap LCA for distance or path queries sometimes pass identical arguments as an edge condition. Your LCA function handles it correctly without modification.
  • Skewed trees and stack depth: A completely skewed tree (every node has only a left child, for example) turns the recursion into O(n) depth. For trees with 10,000+ nodes, that can blow the default stack limit in languages like Python or Java. If your interviewer asks about scale, mention that an iterative approach using parent pointers avoids this. Store each node's parent during a BFS pass, then walk p and q upward until the paths converge. The time stays O(n), but you trade recursion stack for an explicit parent map.
  • Null root: The base case if root is None: return root handles an empty tree. Don't skip it or fold it into the target check. Keeping the null check separate makes the logic cleaner and prevents NoneType attribute errors when accessing root.left or root.right.

These aren't trick cases. They're the natural consequences of the recursion, and talking through them in an interview demonstrates that you've actually traced the algorithm rather than memorised the template.

When a node might not exist (LCA II)

The standard LCA problem guarantees that both p and q exist in the tree. LCA II removes that guarantee. If either node isn't present, return null.

One missing node changes everything.

Why does this break the original algorithm? The standard version returns early when it finds p or q, so it never actually checks whether the other node exists anywhere in the tree. With LCA II, you can't short circuit like that. You need to traverse every node to confirm both targets are actually present.

  1. Python

Instead of returning the node itself, each recursive call returns two booleans tracking whether p and q have been found. The LCA is set only when both are confirmed found for the first time. This catches the false positive that the standard algorithm would produce when p exists but q doesn't.

⚠️ Warning
A common mistake is using the standard LCA algorithm and assuming both nodes exist. If your problem statement doesn't guarantee existence, you need the two boolean tracking approach. Interviewers notice when candidates skip this edge case.

Finding distance between two nodes using LCA

Once you have the LCA, computing the distance between two nodes is a clean decomposition. The distance between p and q equals the depth of p relative to the LCA plus the depth of q relative to the LCA.

  1. Python

The formula works because the path between any two nodes in a tree must pass through their LCA. There's no shorter route. You find the LCA in O(n), then compute two depths with partial traversals from the LCA downward. The total stays O(n).

Remember this decomposition. It converts a path between nodes problem into two simpler root to node problems. The same reduction works for path sums between arbitrary nodes or finding the maximum edge weight on a tree path. Any time you need to reason about a path between two arbitrary nodes, ask yourself whether you can route through the LCA and decompose the problem into two downward traversals.

Binary lifting for repeated queries

The postorder solution is optimal for a single LCA query. But what if you need to answer thousands of LCA queries on the same tree? Running O(n) per query adds up quickly.

Binary lifting preprocesses the tree in O(n log n) time and answers each LCA query after that in O(log n).

For every node, precompute its ancestor at distance 1, 2, 4, 8, 16, and so on (powers of 2). Store these in a table up[node][k], where the entry is the ancestor of that node at distance 2^k.

  1. Python

To find the LCA of two nodes, first bring them to the same depth by jumping the deeper node upward using the precomputed table. Then jump both nodes upward in decreasing powers of 2, skipping any jump that lands them on the same node. When no more jumps are possible, the direct parent of either node is the LCA.

Binary lifting comes up in competitive programming and in problems that combine LCA with other tree queries (path sums, edge weights, tree diameter through specific pairs). Most coding interviews stick to the single query O(n) version. But knowing the sparse table method shows you can think about scaling, and that's the kind of followup senior level interviewers like to explore.

LCA in a binary search tree

If your tree is a BST, you don't need the full postorder traversal. The ordering property gives you a shortcut.

At any node, compare its value against p.val and q.val. If both values are smaller than the current node, the LCA must be in the left subtree. If both are larger, it's in the right subtree. The moment one value falls on each side (or the current node matches one of the targets), you've found the LCA.

  1. Python

This runs in O(h) time, where h is the tree height. For a balanced BST, that's O(log n). No recursion needed, no stack overhead.

The key distinction is that the BST version doesn't visit every node. It follows a single root to answer path, making exactly one comparison per level. The general binary tree version visits every node in the worst case because it has no ordering property to prune with.

Interviewers sometimes ask the general LCA question first, then follow up with "what if it's a BST?" They're testing whether you recognise that additional structure in the input should change your approach. If you give the O(n) postorder solution for a BST problem, it's technically correct but shows you missed the optimization. Always ask whether the tree has any ordering guarantees before committing to an approach.

The skills LCA ties together

The lowest common ancestor pattern ties together several binary tree skills: postorder state propagation, recursive invariant construction, and tree path decomposition. If postorder traversal still feels shaky, start there before attempting LCA variants.

Codeintuition's understanding lesson for the lowest common ancestor pattern walks through the invariant frame by frame before any code. The identification lesson that follows trains you to spot when a problem reduces to LCA, which is the step that usually gets skipped. The full Binary Tree course covers LCA as one of six traversal-based patterns across 47 lessons and 62 problems.

For the complete picture of how tree patterns connect to recursion, traversal, and interview strategy, the full guide to binary trees and recursion covers every pattern in context.

The learning path starts with two permanently free courses on Arrays and Singly Linked Lists that build the recursive thinking foundations tree problems depend on. No trial period, no paywall countdown.

Six months ago, you stared at an LCA problem and tried to work out the recursion from scratch. Where do you start the traversal? What do you return at each level? Now you see an unfamiliar tree problem, recognise the postorder invariant, and write the three line core from understanding. The distance variant and LCA II follow directly. That shift doesn't come from solving more problems. It comes from learning the invariant first.

Learn the LCA invariant before you see the problem

Codeintuition's Binary Tree course teaches the postorder invariant behind LCA with frame by frame visual walkthroughs, then trains you to spot when a problem reduces to it. Start FREE with two foundation courses.

The lowest common ancestor of two nodes p and q is the deepest node in the tree that has both p and q as descendants. A node is allowed to be a descendant of itself, so if p is an ancestor of q, then p is their LCA. The standard recursive postorder approach finds it in O(n) time.
The standard recursive postorder approach runs in O(n) time and O(h) space, where n is the number of nodes and h is the tree height. For repeated queries on the same tree, binary lifting preprocesses in O(n log n) and answers each query in O(log n).
In a BST, you can exploit the ordering property. If both p and q are smaller than the current node, go left. If both are larger, go right. Otherwise, the current node is the LCA. This runs in O(h) time without visiting every node. In a general binary tree, you don't have that ordering, so the postorder approach visits all nodes in the worst case.
LCA II removes the guarantee that both nodes exist in the tree. The standard algorithm would return a false positive if one node is present but the other isn't. LCA II tracks two booleans during traversal to confirm both targets are found before declaring the LCA. This distinction matters when your problem statement says "nodes may or may not exist."
Yes. The distance between nodes p and q equals the depth of p from their LCA plus the depth of q from their LCA. You find the LCA first in O(n), then compute each node's depth from it with a simple traversal. The total time remains O(n) because the depth computation only visits the subtree rooted at the LCA.
Was this helpful?