In standard coding questions, Lowest Common Ancestor (LCA) in a binary tree is solved with post-order recursive DFS in O(N) time. But in production systems (like corporate organization charts or Git commit graph merges), we need to answer thousands of LCA queries per second on the same immutable tree.
Running an O(N) DFS for each query is way too slow. How does Binary Lifting precompute jump tables to answer any LCA query in O(log N) time?
When you have multiple online LCA queries on a static tree, the gold standard is Binary Lifting (used in compiler dominance frontiers, distributed network routing, and Git commit histories).
1. The Core Idea: Powers of 2 Parent Jumps
Instead of storing only a node’s immediate parent (which forces you to step up the tree one node at a time in $O(N)$), what if every node stored its ancestor at distance $2^0, 2^1, 2^2, 2^3, dots, 2^k$?
We define a 2D table:
up[u][i] = the (2^i)-th ancestor of node u.The state transition is pure dynamic programming:
In English: ‘To jump $2^i$ steps up from $u$, first jump $2^{i-1}$ steps up to reach intermediate node $v$, and then from $v$ jump another $2^{i-1}$ steps!’ ($2^{i-1} + 2^{i-1} = 2^i$).
2. Answering an LCA Query in 2 Steps
To find the LCA of nodes
uandv:depth[u] < depth[v], swap them. Use binary powers to jumpuupwards untildepth[u] == depth[v]in $O(log N)$ steps. Ifu == v, they were on the same branch → returnu!uandvupwards together using the largest possible power of 2 such that their ancestors are still different (up[u][i] != up[v][i]). When no more jumps can be made, their immediate parent (up[u][0]) is their Lowest Common Ancestor!Clean C++20 Implementation
Complexity Breakdown
O(N log N)via a single DFS pass.O(N log N)to store the jump table.O(log N). For $N = 1,000,000$, $log_2(1,000,000) pprox 20$ operations. You can evaluate 50,000 queries in a fraction of a second!