Sparse Table
A precomputed table of answers over power-of-two-length blocks that answers idempotent range queries (min, max, gcd) in O(1) after O(n log n) build, for static arrays.
Definition
A sparse table answers range queries on a static array — most famously range minimum (RMQ) — in O(1) per query after an O(n log n) preprocessing step. It works for any associative and idempotent operation: min, max, gcd, bitwise AND/OR. It does not support point updates; for a mutable array use a Segment Tree or Fenwick Tree.
The table st[k][i] stores the answer for the block of length 2^k starting at i: st[0][i] = a[i], and st[k][i] = op(st[k-1][i], st[k-1][i + 2^(k-1)]). A query [l, r] picks k = ⌊log₂(r - l + 1)⌋ and combines two overlapping blocks: op(st[k][l], st[k][r - 2^k + 1]). Overlap is harmless precisely because the operation is idempotent.
For non-idempotent operations like sum, the same table still works with an O(log n) query that decomposes the range into disjoint power-of-two blocks — but a Prefix Sum is simpler for that case.
Intuition
A mental model before the formal terms.
Suppose you want the shortest person in any contiguous group of a line-up, and the line-up never changes. Precompute the shortest in every group of 1, of 2, of 4, of 8, … starting at every position. Now for any range, pick the largest power-of-two group that fits, slide one copy to the left end and one copy to the right end. The two copies overlap in the middle, but that is fine — counting someone twice never changes who the shortest is.
That "counting twice is harmless" is idempotence, and it is why sums do not get the O(1) trick.
How it works
- Build:
K = ⌊log₂ n⌋ + 1levels.st[0] = a. Forkfrom 1 toK-1andifrom 0 whilei + 2^k ≤ n:st[k][i] = op(st[k-1][i], st[k-1][i + 2^(k-1)]). - Log table: precompute
lg[x] = ⌊log₂ x⌋forx = 1…nwithlg[x] = lg[x/2] + 1to avoid floating point in queries. - Idempotent query(l, r):
k = lg[r - l + 1]; returnop(st[k][l], st[k][r - 2^k + 1]). - Non-idempotent query(l, r) (e.g. sum): walk
kfrom high to low; if2^kfits in the remaining range, foldst[k][l]and advancel += 2^k. - To return the index of the minimum (for LCA via Euler tour), store indices and compare
a[idx]. - Memory is
n · log nentries; forn = 10^6that is ~20 million values — fine for ints, watch it for objects.
Why it works
Any range [l, r] of length L is covered by the two blocks [l, l + 2^k - 1] and [r - 2^k + 1, r] with 2^k ≤ L < 2^(k+1), since 2 · 2^k > L. Idempotence (op(x, x) = x) and associativity/commutativity mean the overlap does not alter the result.
Each table cell is derived from two cells one level down, so building is O(n log n) and every cell is exactly the correct fold over its block by induction on k.
Operations
| Operation | Description | Cost |
|---|---|---|
| build(a) | Fill st[k][i] level by level. | O(n log n) |
| query(l, r) — idempotent op | Two overlapping blocks. | O(1) |
| query(l, r) — general op | Decompose into disjoint blocks. | O(log n) |
| point update | Not supported; rebuild. | O(n log n) |
Recognition
How to tell a problem wants this.
- Many queries (
q ≤ 10^5+) of range min/max/gcd/AND/OR on an array that never changes. - "Answer each query in
O(1)" or tight time limits where a segment tree'slog nper query is too slow. - Lowest common ancestor via Euler tour + RMQ.
- Sliding-window min/max over arbitrary (not just fixed-size) windows.
Interactive demo
Play, step, change the input. ← → and space work too.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|---|
| k=0 (len 1) | · | · | · | · | · | · | · | · | · |
| k=1 (len 2) | · | · | · | · | · | · | · | · | · |
| k=2 (len 4) | · | · | · | · | · | · | · | · | · |
| k=3 (len 8) | · | · | · | · | · | · | · | · | · |
1sp[0][i] = a[i]2for k in 1..log n: for i while i + 2^k <= n:3 sp[k][i] = min(sp[k-1][i], sp[k-1][i + 2^(k-1)])4query(l, r): k = floor(log2(r - l + 1))5 return min(sp[k][l], sp[k][r - 2^k + 1]) # two overlapping blocksPseudocode
1build: st[0] = a2 for k in 1..K: for i in 0..n-2^k: st[k][i] = op(st[k-1][i], st[k-1][i + 2^(k-1)])3lg[1] = 0; lg[x] = lg[x/2] + 14query(l, r): k = lg[r-l+1]; return op(st[k][l], st[k][r - 2^k + 1])Implementation
1from typing import Callable2 3 4class SparseTable:5 """O(1) range queries for an idempotent op (min by default) on a static array."""6 71 · State — table st[k][i] = op over a[i .. i + 2^k - 1]82 · Build in O(n log n)9 def __init__(self, a: list[int], op: Callable[[int, int], int] = min):10 self.op = op11 n = len(a)12 self.lg = [0] * (n + 1) # lg[x] = floor(log2 x)13 for i in range(2, n + 1):14 self.lg[i] = self.lg[i // 2] + 115 K = self.lg[n] + 1 if n else 116 self.st = [list(a)]17 for k in range(1, K):18 prev = self.st[k - 1]19 half = 1 << (k - 1)20 self.st.append([op(prev[i], prev[i + half]) for i in range(n - (1 << k) + 1)])21 223 · O(1) query for idempotent ops (min/max/gcd) — overlapping halves are fine23 def query(self, l: int, r: int) -> int:24 """Inclusive range [l, r]."""25 k = self.lg[r - l + 1]26 return self.op(self.st[k][l], self.st[k][r - (1 << k) + 1])27 284 · O(log n) query for non-idempotent ops (sum) — disjoint blocks29 def query_general(self, l: int, r: int, identity: int) -> int:30 res = identity31 k = len(self.st) - 132 while l <= r:33 while k > 0 and l + (1 << k) - 1 > r:34 k -= 135 res = self.op(res, self.st[k][l])36 l += 1 << k37 return resself.lg[i] = self.lg[i // 2] + 1fills floor(log2) for every length up to n.- Each new row is a list comprehension over the previous row combining
prev[i]andprev[i + half]. querylooks upk = lg[r - l + 1]and combines two overlapping blocks — O(1) formin/max/gcd.query_generalshrinkskuntil a block fits, consumes it, and advancesl— O(log n) forsum-like ops.opdefaults to the built-inmin, which accepts exactly two arguments here.
Python lists of lists cost ~28 bytes per int plus pointers; for large n use array or NumPy rows.
math.gcdandoperator.addare ready-made ops;gcdis idempotent,addis not.- The list comprehension in the build is much faster than an explicit loop with
append. 1 << kworks on unbounded ints, so there is no overflow concern for any table size.- For pure-min queries on mutable data,
sortedcontainersor a segment tree is the practical choice.
- Using
math.log2andint()per query — floating error can produce the wrong row for exact powers of two. - Calling
querywith a half-openr(exclusive) — the implementation is inclusive. - Passing
sumasop—sumtakes an iterable, not two scalars; useoperator.add.
- C++ needs a lambda to wrap
std::minas a default argument (overload set); JS/TS passMath.mindirectly and Python passesmin. 1 << kis 32-bit in JS/TS andintin C++; Python shifts are unbounded — none matters below 2^30 elements.- Python's
sumis not a two-argument function; useoperator.add. In C++ usestd::plus<int>(), in JS/TS an arrow(a, b) => a + b. - Memory: C++
introws are 4 bytes/element, JS/TS numbers 8 bytes (or 4 withInt32Array), Python lists ~36 bytes/element.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(1) | O(1) | st[0][i] is the original array. |
| Search | — | — | |
| Insert | — | — | Static; rebuild O(n log n). |
| Delete | — | — | Static; rebuild O(n log n). |
| Update | O(n log n) | O(n log n) | Full rebuild. |
| Build | O(n log n) | O(n log n) | |
| Range min query | O(1) | O(1) | Any idempotent op. |
| Range query (general op) | O(log n) | O(log n) | |
| Space | O(n log n) | ||
Advantages & disadvantages
- True
O(1)queries — nothing beats it for static RMQ in practice. - Simple to implement (two nested loops for the build, three lines per query).
- Works for any idempotent associative operation, and with
O(log n)queries for any associative one.
- Static: any update forces an
O(n log n)rebuild. O(n log n)memory versusO(n)for a segment tree or Fenwick tree.O(1)query needs idempotence; range sums requireO(log n)or a prefix-sum.
Use cases
- Range minimum / maximum queries on immutable arrays.
- LCA queries: RMQ over the Euler tour depths.
- Range GCD, range bitwise AND/OR.
- Binary-lifting-style "jump 2^k steps" tables share the same doubling structure.
- Suffix-array LCP queries between arbitrary suffixes.
- Static array with many range min/max/gcd/AND/OR queries.
- Query time must be
O(1). - LCA via Euler tour when the tree is static.
- The array changes — use a Segment Tree (or Fenwick Tree for prefix-invertible ops).
- Range sums — a Prefix Sum array is
O(n)space andO(1)query. - Memory is tight and
n log nvalues do not fit. - Only a handful of queries — a linear scan per query is simpler.
Alternatives
Common mistakes
- Using the two-overlapping-blocks query for sums (double counts the overlap).
- Building rows for
iwherei + 2^k > n— index out of range. - Computing
kwith floating-pointlog2in a hot loop — precompute an integer log table. - Off-by-one: the second block starts at
r - 2^k + 1, notr - 2^k. - Forgetting that the table is static and mutating the underlying array.
Interview patterns
- Range minimum query with
10^5queries on a fixed array. - Sliding Window Maximum over arbitrary windows (fixed windows are better with a Monotonic Queue).
- LCA in a static tree via Euler tour + RMQ.
- Number of subarrays with GCD/AND equal to k using range queries plus binary search.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Recognizing a sliding-window problemIntermediate
- Prefix sum or segment tree?Intermediate
- Minimum Size Subarray SumIntermediate
- Subarray Sum Equals KIntermediate