A Bit of History
The Lowest Common Ancestor (LCA) problem was first formalized by Aho, Hopcroft, and Ullman in 1973 in the context of string algorithms and data structures. In 1984, Dov Harel and Robert Tarjan made a breakthrough by proving that LCA queries could be answered in \(O(1)\) time after \(O(N)\) preprocessing time, though their original algorithm was notoriously complex. In 2000, Martin Farach-Colton and Michael A. Bender published a famous simplification that reduced LCA to the Range Minimum Query (RMQ) problem over an Euler Tour array using a Sparse Table, making \(O(N)\) preprocessing and \(O(1)\) query time accessible and practical. Meanwhile, Binary Lifting — a dynamic programming technique using powers of two — became the favorite \(O(N \log N)\) preprocessing / \(O(\log N)\) query approach due to its extreme ease of implementation.
Working Principle: Binary Lifting
Binary lifting precomputes a table up[u][j] storing the \(2^j\)-th ancestor of node \(u\). Since any integer depth difference can be uniquely represented as a sum of powers of two (its binary representation), we can jump up the tree in logarithmic steps:
- Depth Equalization: If node \(u\) is deeper than \(v\), lift \(u\) up by jumping \(2^j\) steps at a time until both nodes are at the same depth.
- Same Node Check: If \(u == v\) after depth equalization, then \(u\) (or \(v\)) was an ancestor of the other — return \(u\).
- Simultaneous Jumping: Iterate \(j\) from \(\lfloor \log_2 N \rfloor\) down to 0. Whenever
up[u][j] != up[v][j], jump both \(u\) and \(v\) up by \(2^j\) steps. This stops both nodes right below their lowest common ancestor. - The LCA is then the immediate parent
up[u][0].
Euler Tour + RMQ Reduction
An alternative approach flattens the tree into an array via a DFS traversal (an Euler Tour), recording the node visited at each step and its depth:
- Record the sequence of visited nodes during a DFS traversal. The array has length \(2N - 1\).
- For any two nodes \(u\) and \(v\), locate their first occurrences in the Euler Tour array.
- The LCA of \(u\) and \(v\) corresponds to the node with the **minimum depth** in the range between their first occurrences — reducing LCA to a Range Minimum Query (RMQ) problem!
- Using a **Sparse Table**, RMQ over static arrays can be answered in \(O(1)\) time after \(O(N \log N)\) preprocessing time.
Key Insight
Binary lifting uses dynamic programming: up[u][j] = up[ up[u][j-1] ][j-1]. The 4-th ancestor is the 2-nd ancestor of the 2-nd ancestor. This recurrence allows precomputing all $2^j$ jumps in $O(N \log N)$ time.
Worked Example
Consider a tree with root 1, children 2 and 3. Node 2 has child 4; Node 4 has children 5 and 6. To find LCA(5, 3):
- Depth of 5 is 3; depth of 3 is 1. Depth difference is 2.
- Lift node 5 by 2 steps ($2^1 = 2$):
up[5][1] = 2. Now both nodes (2 and 3) are at depth 1. - Compare 2 and 3:
up[2][0] = 1andup[3][0] = 1(their parents are equal to 1). - The highest jump where
up[2][j] != up[3][j]doesn't exist since they immediately share parent 1. Their LCA is 1.
Correctness
Binary lifting works because any depth difference $d$ can be decomposed into $d = \sum b_i 2^i$ (binary expansion). After equalizing depths, if $u \neq v$, jumping when up[u][j] != up[v][j] guarantees we never jump above or to the LCA prematurely. By the end of the loop, $u$ and $v$ are guaranteed to be direct children of the LCA under different branches.
Complexity Analysis
Comparing the two primary LCA approaches:
| Approach | Preprocessing Time | Query Time | Space Complexity |
|---|---|---|---|
| Binary Lifting | \(O(N \log N)\) | \(O(\log N)\) | \(O(N \log N)\) |
| Euler Tour + Sparse Table (RMQ) | \(O(N \log N)\) | \(O(1)\) | \(O(N \log N)\) |
| Farach-Colton & Bender (\(\pm 1\) RMQ) | \(O(N)\) | \(O(1)\) | \(O(N)\) |
Implementation
Real-World Applications
Phylogenetic Distance & Git Commit Merges
In evolutionary biology, computing the Lowest Common Ancestor on phylogenetic trees identifies the most recent common ancestor between two species. In Version Control Systems like Git, finding the LCA of two commit branches (the "merge base") is the fundamental first step in performing a three-way merge (git merge).
Exercises
- Given two nodes $u$ and $v$ in a weighted tree, show how to calculate the distance between them using LCA: $\text{dist}(u, v) = \text{depth}(u) + \text{depth}(v) - 2 \cdot \text{depth}(\text{LCA}(u, v))$.
- Implement the Euler Tour + Sparse Table (RMQ) reduction for LCA in Python or C++.
- Prove that Tarjan's Offline LCA algorithm (using Union-Find) answers all $Q$ pre-given LCA queries in $O(N + Q \cdot \alpha(N))$ time.
- Challenge: Modify Binary Lifting to support online addition of leaf nodes to a dynamic tree.
Limitations
Memory & Tree Dynamics
Binary lifting requires $O(N \log N)$ memory for the jump table. Furthermore, if the tree structure changes dynamically (edges inserted/deleted), standard binary lifting requires full re-computation; dynamic trees require Link-Cut Trees or Euler Tour Trees instead.