๐Ÿฆ€ Functional Rust

797: Range Minimum Query (Sparse Table)

Difficulty: 4 Level: Advanced After O(n log n) preprocessing, answer "what's the minimum in `arr[l..=r]`?" in O(1) โ€” using a sparse table.

The Problem This Solves

You have a static array and need to answer thousands of queries of the form "what's the minimum value between index `l` and index `r`?" Naively scanning is O(n) per query โ€” too slow when you have millions of queries. Segment trees answer in O(log n) but add implementation complexity. The sparse table answers range minimum in O(1) after O(n log n) preprocessing, with no updates needed. It's the fastest structure for static RMQ. Real-world uses: suffix array construction (LCP queries during string matching), computational geometry (lowest common ancestor in trees reduces to RMQ), sliding window minimum in stream processing, and interval scheduling queries. Any algorithm that repeatedly asks "what's the minimum in this range of a fixed array?" benefits from sparse table preprocessing. The structure exploits one beautiful property of `min`: it's idempotent โ€” `min(x, x) = x`. This means overlapping intervals can be combined freely without double-counting. You can cover any range `[l, r]` with just two overlapping power-of-two intervals and take the min of both, because repeated elements don't change the minimum.

The Intuition

Build a table where `table[k][i]` = minimum of the `2^k` elements starting at index `i`. Level 0 is the original array. Level k is built from level k-1: `table[k][i] = min(table[k-1][i], table[k-1][i + 2^(k-1)])`. For a query `[l, r]`: find `k = floor(logโ‚‚(r-l+1))`. The two intervals `[l, l+2^k-1]` and `[r-2^k+1, r]` both have length `2^k`, both fit inside `[l,r]`, and together cover all of `[l,r]`. The answer is `min(table[k][l], table[k][r - 2^k + 1])`. O(1) query, O(n log n) build, O(n log n) space.

How It Works in Rust

struct SparseTable {
 table: Vec<Vec<i64>>,  // table[k] = array of mins for windows of size 2^k
 log2:  Vec<usize>,     // precomputed floor(log2(i)) for fast lookup
}

impl SparseTable {
 fn build(arr: &[i64]) -> Self {
     let n = arr.len();
     // Number of levels = ceil(log2(n)) + 1
     let levels = usize::BITS as usize - n.leading_zeros() as usize;
     let mut table = vec![arr.to_vec()];  // level 0 = original array

     for k in 1..levels {
         let prev = &table[k - 1];
         let half = 1 << (k - 1);   // 2^(k-1)
         // Each row k is shorter: windows of size 2^k need at most n - 2^k + 1 starts
         let row: Vec<i64> = (0..n.saturating_sub((1 << k) - 1))
             .map(|i| prev[i].min(prev[i + half]))
             .collect();
         table.push(row);
     }

     // Precompute log2 table: log2[i] = floor(log2(i))
     let mut log2 = vec![0usize; n + 1];
     for i in 2..=n { log2[i] = log2[i / 2] + 1; }

     SparseTable { table, log2 }
 }

 fn query(&self, l: usize, r: usize) -> i64 {
     let k = self.log2[r - l + 1];           // largest power-of-two โ‰ค window size
     // Two overlapping windows of size 2^k โ€” idempotence makes overlap safe
     self.table[k][l].min(self.table[k][r + 1 - (1 << k)])
 }
}
The `leading_zeros()` trick computes `ceil(log2(n))` without floating point. Each table row shrinks as `k` grows โ€” the inner `Vec` lengths decrease by half each level, keeping total space at O(n log n). The log2 precomputation turns the `query`'s level selection into a single array lookup.

What This Unlocks

Key Differences

ConceptOCamlRust
2D ragged table`Array.init levels (fun k -> ...)``Vec<Vec<i64>>` โ€” push each level
Bit manipulation`1 lsl k``1 << k` โ€” same idiom
Leading zeros`Int.clz` (OCaml 5.x)`n.leading_zeros()` โ€” built-in on all integer types
Log precomputationRecursive `let rec`Iterative `for i in 2..=n` โ€” both O(n)
// Range Minimum Query โ€” Sparse Table O(n log n) build, O(1) query

struct SparseTable {
    table: Vec<Vec<i64>>,
    log2: Vec<usize>,
}

impl SparseTable {
    fn build(arr: &[i64]) -> Self {
        let n = arr.len();
        let levels = if n > 1 { usize::BITS as usize - n.leading_zeros() as usize } else { 1 };
        let mut table = vec![arr.to_vec()];
        for k in 1..levels {
            let prev = &table[k - 1];
            let half = 1 << (k - 1);
            let row: Vec<i64> = (0..n.saturating_sub((1 << k) - 1))
                .map(|i| prev[i].min(prev[i + half]))
                .collect();
            table.push(row);
        }
        // Precompute floor(log2(i)) for i = 0..n
        let mut log2 = vec![0usize; n + 1];
        for i in 2..=n { log2[i] = log2[i / 2] + 1; }
        SparseTable { table, log2 }
    }

    fn query(&self, l: usize, r: usize) -> i64 {
        let k = self.log2[r - l + 1];
        self.table[k][l].min(self.table[k][r + 1 - (1 << k)])
    }
}

fn main() {
    let arr = vec![2i64, 4, 3, 1, 6, 7, 8, 9, 1, 7];
    let st  = SparseTable::build(&arr);
    println!("Array: {:?}", arr);
    println!("RMQ(0,9) = {}  (expect 1)", st.query(0, 9));
    println!("RMQ(1,5) = {}  (expect 1)", st.query(1, 5));
    println!("RMQ(2,4) = {}  (expect 1)", st.query(2, 4));
    println!("RMQ(6,9) = {}  (expect 1)", st.query(6, 9));
}
(* Range Minimum Query โ€” Sparse Table O(n log n) build, O(1) query *)

let build_sparse arr =
  let n = Array.length arr in
  let log2 = if n > 1 then int_of_float (log (float_of_int n) /. log 2.0) + 1 else 1 in
  let table = Array.init log2 (fun k ->
    Array.init n (fun i ->
      if k = 0 then arr.(i)
      else min max_int max_int (* filled below *)
    )
  ) in
  table.(0) <- Array.copy arr;
  for k = 1 to log2 - 1 do
    for i = 0 to n - (1 lsl k) do
      table.(k).(i) <- min table.(k-1).(i) table.(k-1).(i + (1 lsl (k-1)))
    done
  done;
  table

let query table l r =
  let len = r - l + 1 in
  let k   = int_of_float (log (float_of_int len) /. log 2.0) in
  min table.(k).(l) table.(k).(r - (1 lsl k) + 1)

let () =
  let arr   = [| 2; 4; 3; 1; 6; 7; 8; 9; 1; 7 |] in
  let table = build_sparse arr in
  Printf.printf "Array: ";
  Array.iter (fun x -> Printf.printf "%d " x) arr;
  print_newline ();
  Printf.printf "RMQ(0,9) = %d  (expect 1)\n" (query table 0 9);
  Printf.printf "RMQ(1,5) = %d  (expect 1)\n" (query table 1 5);
  Printf.printf "RMQ(2,4) = %d  (expect 1)\n" (query table 2 4);
  Printf.printf "RMQ(6,9) = %d  (expect 1)\n" (query table 6 9)