Sqrt Tree — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Build your own working knowledge by writing the code; compare against the reference implementations in
junior.mdSection 9 andinterview.md. If your code is slow at scale, profile and iterate.The recurring theme: Sqrt Tree gives O(1) range queries for any associative op (including non-idempotent sum/product/xor) after O(n log log n) build, with O(√n) point updates. Every task should be validated against a brute-force loop.
Beginner Tasks¶
Task 1 — Range sum Sqrt Tree from scratch¶
Implement a Sqrt Tree over op = sum supporting query(l, r) = arr[l] + ... + arr[r] in O(1) after O(n log log n) build.
- Constraints: static array; inclusive
[l, r]; validate0 ≤ l ≤ r < n. - Test:
arr = [1,3,2,7,9,11,6,4,5,1,8,2,10,3,7,5];query(2,13)→ 68;query(0,15)→ 84;query(7,7)→ 4. - Evaluation: brute-force every
(l, r)pair and assert equality.
Go¶
package main
func main() {
// implement SqrtTree with op = func(a,b int) int { return a+b }
// validate query(l,r) == sum(arr[l:r+1]) for all l<=r
}
Java¶
public class Task1 {
public static void main(String[] args) {
// implement SqrtTree with op = Integer::sum; validate vs brute force
}
}
Python¶
def main():
# implement SqrtTree(arr, lambda a,b: a+b); validate vs sum(arr[l:r+1])
pass
if __name__ == "__main__":
main()
Reference: junior.md Section 9 (full Go/Java/Python) and interview.md coding challenge.
Task 2 — Range min Sqrt Tree (idempotent op)¶
Same structure, but op = min. Confirm the identical code works just by swapping the op — the whole point of an op-agnostic structure.
- Constraints: static; inclusive ranges.
- Test:
arr = [3,1,4,1,5,9,2,6];query(0,7)→ 1;query(4,5)→ 5;query(6,7)→ 2. - Reflect: for min/max/gcd a sparse table is simpler and equally fast (O(1)); note in a comment that Sqrt Tree's unique value is the non-idempotent case.
Provide starter code in all 3 languages (copy Task 1, pass min instead of +).
Task 3 — Range xor Sqrt Tree (non-idempotent — sparse table can't)¶
Implement with op = xor. This is a non-idempotent op, so a sparse table would give wrong answers; Sqrt Tree is correct.
- Constraints: static; inclusive ranges.
- Test:
arr = [5,7,2,9,4,8,1,6]; verifyquery(l, r)equals the running xor ofarr[l..r]for several ranges (e.g.query(1,4)= 7^2^9^4 = 8). - Evaluation: brute force the xor for all
(l, r)and assert equality. - In a comment: explain why a sparse table fails here (overlapping windows double-xor, and
x^x=0 ≠ x).
Provide starter code in all 3 languages.
Task 4 — Single-element and two-element edge cases¶
Harden your Task 1 implementation against boundary inputs.
- Test cases:
arr = [42],query(0, 0)→ 42 (n = 1, no layers built).arr = [10, 20],query(0, 1)→ 30;query(1, 1)→ 20.arr = [5, 5, 5, 5],query(1, 3)→ 15.arr = [-3, -1, -9, 4]withop = min,query(0, 3)→ -9.- Constraints: must not crash on
n = 0orn = 1;query(l, l)must short-circuit toarr[l]. - Evaluation: all of the above pass; add an assertion that querying
l == rreturnsarr[l]without descending layers.
Provide starter code in all 3 languages.
Task 5 — Build-table inspector¶
Augment your Sqrt Tree to print, for the top layer, the prefix array, suffix array, and the between table. This makes the three-piece query trick concrete.
- Test: on
arr = [1,3,2,7,9,11,6,4,5,1,8,2,10,3,7,5]withop = sum, print the top-layer prefix, suffix, and the 4×4 between table; confirm they match the tables injunior.mdSection 8 (between[0][3] = 84, etc.). - Constraints: read-only inspection; do not change query behavior.
- Evaluation: printed tables match the worked walkthrough.
Provide starter code in all 3 languages.
Intermediate Tasks¶
Task 6 — Generic op via a function parameter¶
Refactor so the op is injected at construction (func(a,b) -> c in each language) rather than hard-coded. Demonstrate the same instance class answering sum, product (mod 1e9+7), gcd, and bitwise-OR on the same array.
- Constraints: zero structural change between ops — only the injected function differs.
- Test: on
arr = [2,3,5,7,11,13]: sumquery(0,5)= 41; product mod 1e9+7query(0,5)= 30030; gcdquery(0,5)= 1; ORquery(0,5)= 15. - Evaluation: all four op instances pass brute-force checks.
Provide starter code in all 3 languages.
Task 7 — Point update in O(√n)¶
Add update(i, v) that sets arr[i] = v and rebuilds only i's block at each layer (prefix/suffix + the affected between row/column), achieving O(√n).
- Reference:
senior.mdSection 11 (update path code in all three languages). - Test:
arr = [1,3,2,7,9,11,6,4,5,1,8,2,10,3,7,5], op = sum.query(2,13)→ 68. Afterupdate(6, 100):query(2,13)→ 162;query(5,6)→ 111. - Constraints: must NOT do a full O(n log log n) rebuild; only
i's block per layer. - Evaluation: after a sequence of random updates, every range query still matches brute force.
Provide starter code in all 3 languages.
Task 8 — Range matrix product¶
Store 2×2 matrices (mod 1e9+7) and pass matrix multiply as the op. Answer query(l, r) = product of matrices l..r in O(1).
- Reference:
middle.mdSection 8.2 (matrix multiply in all three languages). - Test: build over
nFibonacci transition matrices[[1,1],[1,0]];query(0, k-1)should yield the matrix encodingF(k+1), F(k). Spot-check against direct multiplication for k = 1..10. - Constraints: matrix multiply is associative but NOT idempotent — emphasize this is a Sqrt-Tree-only O(1) use case.
- Evaluation: matches brute-force matrix-chain product for random ranges.
Provide starter code in all 3 languages.
Task 9 — Benchmark vs segment tree¶
Implement a standard segment tree (range sum + point update) and benchmark query throughput against your Sqrt Tree at n = 10^4, 10^5, 10^6 with 10^6 random range-sum queries.
- Constraints: identical query workload for both structures; warm up before timing.
- Expected pattern: Sqrt Tree's O(1) query overtakes the segment tree's O(log n) as
ngrows and the segment tree starts missing cache on its descent; at smallnthe segment tree's cheaper build may win end-to-end. - Deliverable: a table of build time and per-query time for both, plus a one-paragraph explanation of the crossover (cache misses, build amortization).
Provide starter code in all 3 languages (timing harness as in the TEMPLATE benchmark block).
Task 10 — Memory accounting¶
Instrument your Sqrt Tree to count the exact number of stored cells (prefix + suffix + between, summed over all layers) for n = 16, 256, 4096, 10^6.
- Constraints: count cells, not bytes; verify the between table is O(n) per layer, NOT O(n²).
- Expected: total cells ≈
c · n · log log nfor a small constantc(~10–15). Plot or tabulate cells/n vs n; it should grow likelog log n(very slowly), not likelog n. - Evaluation: confirm no layer's between table exceeds O(n); flag it loudly if it does (that's the classic O(n²) bug).
Provide starter code in all 3 languages.
Advanced Tasks¶
Task 11 — onLayer table for true O(1) query¶
Replace the recursive layer descent with an onLayer[h] lookup (h = msb(l XOR r)) so the separating layer is chosen in O(1), making the query worst-case O(1) rather than O(log log n).
- Reference:
professional.mdSection 6 (onLayer construction). - Constraints: the query must perform a single table lookup to pick the layer, then the three-piece combine — no descent loop.
- Test: results identical to the descent version on random
(l, r); measure that query latency is flat regardless of range length. - Evaluation: correctness vs brute force + a microbenchmark showing constant latency across range sizes.
Provide starter code in all 3 languages.
Task 12 — Lazy batched updates¶
Extend Task 7: instead of rebuilding a block on every update, mark it dirty and rebuild once (O(√n)) at the next query that touches it. Show that a burst of m updates to one block costs O(√n), not O(m√n).
- Reference:
professional.mdSection 8.1 (amortization argument). - Constraints: queries must trigger a flush of any dirty block they read before combining.
- Test: apply 1000 updates to indices within one block, then one query; assert only one block rebuild occurred (instrument a counter) and the answer is correct.
- Evaluation: correctness + the rebuild counter confirms coalescing.
Provide starter code in all 3 languages.
Task 13 — Sharded Sqrt Tree¶
Split a large array into K contiguous shards, each with its own Sqrt Tree. Answer a range query [l, r] that spans shards by combining per-shard partial results with op.
- Reference:
senior.mdSection 10 (sharding design). - Constraints: each per-shard sub-query is O(1) locally; the coordinator combines O(K) partials with
op(correct becauseopis associative). - Test: shard
arr(n = 10^5) into K = 10 shards; verify cross-shardquery(l, r)matches a single unsharded Sqrt Tree for random ranges. - Evaluation: identical answers to the unsharded version.
Provide starter code in all 3 languages.
Task 14 — Index-returning range min/max¶
Modify the op so query(l, r) returns the index achieving the min (or max), not the value. Store (value, index) and define op((va,ia),(vb,ib)) = (va,ia) if va<=vb else (vb,ib).
- Constraints: ties broken by smallest index.
- Test:
arr = [3,1,4,1,5,9,2,6], range-min-indexquery(0,7)→ 1 (first index of value 1);query(2,7)→ 3. - Evaluation: matches brute-force argmin over all ranges.
Provide starter code in all 3 languages.
Task 15 — Crossover study: Sqrt Tree vs sparse table vs segment tree¶
For an idempotent op (range gcd), implement all three structures and find, empirically, the (n, query-count) region where each is fastest end-to-end (build + queries).
- Constraints: identical workloads; report build time, per-query time, and total memory for each at
n = 10^4, 10^5, 10^6, 10^7. - Expected findings: sparse table simplest and fast but heaviest memory (O(n log n)); Sqrt Tree lighter memory (O(n log log n)) with O(1) queries; segment tree cheapest build and memory but O(log n) queries. The right choice depends on memory budget and query count.
- Deliverable: a comparison table + a short recommendation paragraph per regime.
Provide starter code in all 3 languages.
Benchmark Task¶
Compare Sqrt Tree query performance across all 3 languages on a fixed workload (op = sum, n = 10^6, 2·10^6 random queries).
Go¶
package main
import (
"fmt"
"time"
)
func main() {
n := 1_000_000
data := make([]int, n)
for i := range data {
data[i] = i % 1000
}
// st := New(data, func(a, b int) int { return a + b })
start := time.Now()
// run 2_000_000 random queries against st.Query
elapsed := time.Since(start)
fmt.Printf("Go: %.3f ms total\n", float64(elapsed.Milliseconds()))
}
Java¶
public class Benchmark {
public static void main(String[] args) {
int n = 1_000_000;
int[] data = new int[n];
for (int i = 0; i < n; i++) data[i] = i % 1000;
// SqrtTree st = new SqrtTree(data, Integer::sum);
long start = System.nanoTime();
// run 2_000_000 random queries against st.query
long elapsed = System.nanoTime() - start;
System.out.printf("Java: %.3f ms total%n", elapsed / 1_000_000.0);
}
}
Python¶
import time, random
def main():
n = 1_000_000
data = [i % 1000 for i in range(n)]
# st = SqrtTree(data, lambda a, b: a + b)
queries = [(random.randint(0, n - 1), 0) for _ in range(2_000_000)]
queries = [(min(l, n - 1), random.randint(min(l, n - 1), n - 1)) for l, _ in queries]
start = time.perf_counter()
# for l, r in queries: st.query(l, r)
print(f"Python: {(time.perf_counter() - start) * 1000:.3f} ms total")
if __name__ == "__main__":
main()
Self-check¶
After completing the tasks you should be able to:
- Implement a Sqrt Tree for range sum from a blank file, validated vs brute force.
- Swap the op (min, product, gcd, xor, matrix) with zero structural change.
- Explain why a sparse table fails for sum/product/xor and Sqrt Tree does not.
- Implement an O(√n) point update that rebuilds only the affected block per layer.
- State why the depth is log log n and the build is O(n log log n).
- Avoid the O(n²) between-table memory bug.
- Replace descent with an
onLayerlookup for worst-case O(1) queries. - Decide, with numbers, when Sqrt Tree beats a segment tree or a sparse table.
If any item is fuzzy, re-read the matching section of junior.md / middle.md / senior.md / professional.md and try the task again.
Bonus — bring your own problem¶
Find a competitive-programming problem where a static, non-idempotent associative range query is the bottleneck and solve it with a Sqrt Tree:
- Range product / range xor over a fixed array with many queries.
- Range gcd on a very large array where a sparse table would exceed the memory limit.
- Range matrix product for batched linear-recurrence evaluation.
Solving 2–3 end-to-end locks in the pattern far better than reading alone.
In this topic
- interview
- tasks