Lowest Common Ancestor & Binary Lifting
Given a rooted tree, the lowest common ancestor of and is the deepest node that is an ancestor of both. A naive walk answers one query in ; binary lifting precomputes the -th ancestor of every node in , then answers -th-ancestor and LCA queries in each.
╌╌╌╌
The previous lessons gave us a rooted tree and a single root-to-node path for each vertex. Many problems instead concern two vertices at once: the distance between and , the highest fork their paths share, the smallest region containing two nested regions. Each reduces to the lowest common ancestor.
The LCA is well defined and unique: the sets of ancestors of and of are each a chain from the root, so their intersection is a chain, and a finite chain has a unique deepest element.
The naive walk
If every node stores a parent pointer and a depth, one query is easy. Lift the deeper of until both sit at the same depth, then advance both pointers up in lockstep; the first node they agree on is the LCA.
- 1while do
- 2
- 3while do
- 4
- 5while do
- 6
- 7
- 8return
This needs no preprocessing and is correct, but each step moves up one edge, so a query costs where is the tree's height. On a balanced tree , but on a degenerate path , and queries cost . We want a query cost that does not depend on shape. (For the asymptotic notation, see asymptotic analysis.)
Binary lifting
To address this, make each jump cover an exponentially larger distance. Instead of
go up one,
precompute, for every node and every , a pointer that goes up
edges at once.
The whole table is built from a single doubling identity: climbing edges is climbing edges twice.
So column of the table is computed entirely from column , one pass per power of two. The number of columns is , since no node has an ancestor more than edges up.
- 1run a DFS/BFS from the root to fill and
- 2for each node do
- 3root points to itself
- 4for to do
- 5for each node do
- 6
The table has entries and each costs , so preprocessing is time and space.
-th ancestor in
Any non-negative integer has a unique binary expansion, so the climb of
edges decomposes into jumps of size , one jump per set bit.
Take each set bit from low to high and follow the matching column of up.
- 1for to do
- 2if has bit set then
- 3
- 4if then return nilran off the root
- 5return
At most bits are set, so this is . The order of the jumps does not matter for the destination (they compose to the same total climb), but processing low bits first keeps the running node well-defined at each step.
LCA in
The LCA query reuses the same jumps as two phases. Phase 1 lifts the deeper node up by exactly , a single call, so and sit at equal depth. If they now coincide, one was an ancestor of the other and we are done. Phase 2 lifts both nodes simultaneously: scanning from high to low, we jump both up by only when that keeps them distinct. When the loop ends, and are the two distinct children-side nodes just below the LCA, so the answer is their common parent.
- 1if then swap
- 2phase 1
- 3if then return
- 4for downto dophase 2
- 5if then
- 6
- 7
- 8returntheir common parent
The depth-equalizing jump is and the second loop runs times, so each LCA query is after the one-time build.
Here and already share depth; both lift to and (kept distinct),
then one parent step lands on , drawn in acc.
A worked example
The whole method lives in the up grid, so we build one in full. Root the
twelve-node tree below at node ; depths run from at the root to at
node .
With we get , but the deepest node sits only edges from the root and , so columns through already saturate: column would repeat column exactly (every -jump already lands on the root). We show columns . Rows are nodes, columns are , each entry is the -th ancestor, and the root's pointers stay at the root itself.
The build fills this grid one column at a time, left to right, and every entry is two array reads. Row shows the doubling in action:
- (from the DFS);
- : two -jumps make a -jump;
- : two -jumps make a -jump;
- — and 's th ancestor clamps to the root, since is only deep.
No entry ever looks at the tree again; column reads only column .
A -th-ancestor query, bit by bit
Find the th ancestor of node . Write : bits and are set, bit is clear. scans the bits low to high:
- bit set: : climbed edge, to go;
- bit clear: skip column ;
- bit set: : climbed more edges.
Answer: node . That checks out: , so its th ancestor is exactly the root. The two table cells touched are the ones highlighted in acc above: two reads answered a -edge climb.
An LCA query, phase by phase
Now run in full. Depths are and , so is deeper.
Phase 1 (equalize). Lift by : one jump, . Both nodes now sit at depth . They differ (), so the LCA is strictly above and phase 2 runs.
Phase 2 (simultaneous lift). Scan down to , jumping both nodes only when their -th ancestors differ:
- : and : equal, so an -jump would overshoot the LCA; skip.
- : and : equal again (-jumps from depth also land on the root); skip.
- : and — different, safe to jump: , , both now at depth .
- : and — different again: , , depth .
The loop ends with and , the two children of the LCA, and the algorithm returns . Correct: nodes and hang from different subtrees of the root, so .
The two skipped levels follow from the greedy construction. From depth the LCA sits edges up on each side, so the loop must climb exactly edges before the final parent step, and selects the and jumps while rejecting and as overshoots — the binary expansion of , computed without ever knowing .
The costs, exactly
The preprocessing and query bounds come from counting table reads.
- Build. The DFS fills and in . The table has entries with , each computed by one composition, so the build does constant-time steps: time, and the same in space since the table persists.
- -th ancestor. One jump per set bit of , at most jumps, each one array read: .
- LCA. Phase 1 is one -th-ancestor call ( reads). Phase 2 tests every level once — exactly comparisons, each two reads, with at most jumps taken — then one final read. In total at most table reads per query.
Concretely, at : , the table holds entries (about MB at bytes each), and a query costs at most array reads — against up to pointer steps for the naive walk on a path-shaped tree. The method trades memory for query time, and on large inputs memory is the binding constraint.
Application: tree distance and path queries
LCA turns a two-vertex path question into arithmetic on depths. The unique path from to in a tree goes up from to and back down to , so its length is
On the worked tree, , and counting edges along confirms it: nine edges.
Each query is one LCA plus work, hence . The same decomposition
answers is on the path?
, aggregates a value along the path (split
into the two vertical legs), or, combined with , emits
step-by-step U/L/R directions: climb steps
up, then walk the recorded downward path to .
Alternatives
Binary lifting is the most broadly useful LCA method, but two alternatives beat it on specific query models.1
- Euler tour + sparse-table RMQ. Record the Euler traversal of the tree (each
node appended on entry and after each child returns); within it, the LCA of
and is the shallowest node visited between any occurrence of and of
. That reduces LCA to a range-minimum query over the depth array, which a
sparse table answers in after an build.2 So queries
drop to , but the structure is static and does not directly give -th
ancestors.
The reduction is best seen laid out. Below the tree, the Euler tour writes each node as it is entered and re-entered, with its depth underneath. The LCA of and is the shallowest entry anywhere between an occurrence of and one of — i.e. the minimum of that depth subarray (shaded), which here is :
The query comes from covering the range with two overlapping blocks. Precompute, for every index and power , the minimum of the length- block starting at ( entries, each from two smaller blocks). A query range of length is then covered by two overlapping blocks of length , one flush left and one flush right; minimum is idempotent, so the overlap does no harm, and the answer is the smaller of two precomputed values. On a six-node tree ( has children ; node has children ; node has child ) the tour has entries, and the query spans tour indices through — length , block length :
Block covers indices with depth minimum ; block covers with depth minimum . The smaller is , at index , so the LCA is node — found with two lookups and one comparison, whatever the size of the tree.
- Tarjan's offline LCA. If all query pairs are known in advance, a single DFS with a union-find structure answers them in near-linear total time, processing each query when its second endpoint is first reached.3
Pitfalls
Binary lifting is short to write and easy to get subtly wrong. The recurring bugs:
- too small. The table must reach the deepest possible climb: must
be at least the tree height, and height can be . Hard-coding
K = 17for () fails exactly on path-shaped inputs — and only there, since random trees are shallow, so tests on random trees pass. Use (or , which never under-shoots) and compute it from . - Off-by-one in the level loops. The columns are through inclusive: the build loop runs and the query loops touch bit
and level . Writing
for k in 1..K-1or scanning bits below silently halves the maximum jump, another bug invisible on shallow tests. - Inconsistent root sentinel. Either (jumps saturate
at the root, as in this lesson) or (overshoots
are detectable, but every build read must guard nil). With the saturating
convention, cannot tell
landed on the root
fromran past it
— compare with first if the difference matters. Mixing the two conventions dies on . - Skipping the coincidence check after phase 1. If equalizing depths makes , that node is the LCA. Let phase 2 run anyway and every level test compares equal, so nothing jumps and the return hands back the LCA's parent — one node too high.
- Jumping while instead of while . The phase-2 test must look one jump ahead. Jumping whenever the current nodes differ lets a big jump land both on the LCA (or above it, where all ancestors agree), and the final then overshoots. Keeping the nodes strictly below the LCA is the loop's invariant; the test enforces it.
- Building columns in the wrong order. reads at another node, so the whole of column must exist before column starts: the loop goes outside, the node loop inside. Swapping them reads half-built entries.
Constant-time LCA and where it hides
The theoretical optimum. Binary lifting answers each query in ; the Euler-tour-plus-RMQ reduction in the alternatives above already reaches per query after preprocessing — the RMQ instance it produces has the special property (adjacent Euler-tour depths differ by exactly one), which the Bender-Farach-Colton method exploits to get true linear preprocessing.4 So LCA is, asymptotically, a solved problem: linear build, constant query. Binary lifting remains the common choice in practice anyway, because it also answers -th ancestor and level-ancestor queries, is trivial to code correctly, and its query is fast enough that the machinery's larger constants rarely pay off.
Offline in near-linear total time. When every query is known in advance, Tarjan's offline algorithm answers all of them in one DFS with a union-find structure, for total — effectively linear.5 As DFS finishes a subtree it unions it into its parent's set, and a query is resolved the moment the second endpoint is reached: the answer is of the other endpoint's set representative. It is the method of choice for batch workloads like compiler dominator trees and phylogenetics, where all queries arrive together.
Why LCA is everywhere. The lowest common ancestor is a primitive far beyond tree puzzles. Distance in a tree, , turns any path-length query into one LCA lookup. Suffix trees use LCA on the tree of suffixes to find the longest common extension of two positions in , which underlies fast string matching and the longest common prefix
arrays of suffix automata. Version-control systems compute the merge base of two commits as an LCA in the commit DAG (generalized to directed acyclic graphs). And range-minimum queries and LCA are interreducible (each solves the other in linear time), so a fast LCA is also a fast RMQ and vice versa.
Takeaways
- The lowest common ancestor of and is the deepest node ancestral to both; it is unique because ancestor sets are root-chains.
- The naive walk (equalize depth, climb together) needs no preprocessing but costs per query, or on a degenerate tree.
- Binary lifting precomputes , the -th ancestor, via the doubling identity in time and space.
- A -th-ancestor query jumps by each -bit of ; an LCA query lifts the deeper node to equal depth, then jumps both up by decreasing powers of two while they stay distinct — each .
- Tree distance is , turning path queries into arithmetic.
- Alternatives: Euler tour + sparse-table RMQ gives queries on a static tree; Tarjan's union-find DFS answers all queries offline in near-linear time. Binary lifting wins on being online and also serving -th ancestors.
- The classic bugs are boundary bugs — too small for path-shaped trees, levels looped to , the missing check after depth equalization — and most stay invisible on random (hence shallow) test trees.
Footnotes
- Skiena, § — Trees / LCA: survey of LCA strategies and the preprocessing/query trade-off across query models. ↩
- Erickson, Ch. — Trees: the Euler-tour reduction of LCA to range-minimum, with sparse-table RMQ giving queries after preprocessing. ↩
- CLRS, Ch. — (trees): Tarjan's offline LCA via depth-first search and disjoint-set union, near-linear in . ↩
- Bender, M. A. & Farach-Colton, M. (2000),
The LCA Problem Revisited,
Proc. LATIN 2000, 88–94 — linear-preprocessing, constant-query LCA via the RMQ reduction. ↩ - Tarjan, R. E. (1979),
Applications of path compression on balanced trees,
Journal of the ACM 26(4), 690–715 — the offline union-find LCA algorithm. ↩
╌╌ END ╌╌