797-range-minimum-query — Range Minimum Query (Sparse Table)
Tutorial Video
Text description (accessibility)
This video demonstrates the "797-range-minimum-query — Range Minimum Query (Sparse Table)" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Range Minimum Query (RMQ) asks: given an array and a query `(l, r)`, what is the minimum element in `arr[l..=r]`? Key difference from OCaml: 1. **Immutability advantage**: Sparse Table's O(1) query relies on immutability; Rust's ownership system naturally prevents mutation after construction.
Tutorial
The Problem
Range Minimum Query (RMQ) asks: given an array and a query (l, r), what is the minimum element in arr[l..=r]? Naive O(n) per query is too slow for many applications. The Sparse Table data structure preprocesses the array in O(n log n) time to answer RMQ queries in O(1) using overlapping power-of-two ranges. RMQ is used in LCA (Lowest Common Ancestor) algorithms for trees, suffix array construction, and competitive programming.
🎯 Learning Outcomes
table[j][i] = index of minimum in arr[i..i+2^j]log[i] = floor(log2(i)) for O(1) query dispatchquery(l, r) by overlapping two ranges of size 2^k where k = log[r-l+1]min(min(a,b), min(b,c)) = min(a,b,c))Code Example
#![allow(clippy::all)]
//! # Range Minimum Query (Sparse Table)
pub struct SparseTable {
table: Vec<Vec<usize>>,
log: Vec<usize>,
}
impl SparseTable {
pub fn new(arr: &[i32]) -> Self {
let n = arr.len();
let k = (n as f64).log2().floor() as usize + 1;
let mut table = vec![vec![0; n]; k];
let mut log = vec![0; n + 1];
for i in 2..=n {
log[i] = log[i / 2] + 1;
}
for i in 0..n {
table[0][i] = i;
}
for j in 1..k {
for i in 0..=(n - (1 << j)) {
let left = table[j - 1][i];
let right = table[j - 1][i + (1 << (j - 1))];
table[j][i] = if arr[left] <= arr[right] { left } else { right };
}
}
Self { table, log }
}
pub fn query(&self, arr: &[i32], l: usize, r: usize) -> usize {
let j = self.log[r - l + 1];
let left = self.table[j][l];
let right = self.table[j][r - (1 << j) + 1];
if arr[left] <= arr[right] {
left
} else {
right
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rmq() {
let arr = [1, 3, 2, 7, 9, 11];
let st = SparseTable::new(&arr);
assert_eq!(st.query(&arr, 0, 2), 0);
}
}Key Differences
OCaml Approach
OCaml implements the sparse table with Array.make_matrix k n 0. The log precomputation uses a for loop over indices. OCaml's log2 function with int_of_float computes the floor log2. min is the stdlib comparison function. The query function is a two-line function selecting the minimum of two table entries. Segment trees (a related data structure) are common in OCaml competitive programming.
Full Source
#![allow(clippy::all)]
//! # Range Minimum Query (Sparse Table)
pub struct SparseTable {
table: Vec<Vec<usize>>,
log: Vec<usize>,
}
impl SparseTable {
pub fn new(arr: &[i32]) -> Self {
let n = arr.len();
let k = (n as f64).log2().floor() as usize + 1;
let mut table = vec![vec![0; n]; k];
let mut log = vec![0; n + 1];
for i in 2..=n {
log[i] = log[i / 2] + 1;
}
for i in 0..n {
table[0][i] = i;
}
for j in 1..k {
for i in 0..=(n - (1 << j)) {
let left = table[j - 1][i];
let right = table[j - 1][i + (1 << (j - 1))];
table[j][i] = if arr[left] <= arr[right] { left } else { right };
}
}
Self { table, log }
}
pub fn query(&self, arr: &[i32], l: usize, r: usize) -> usize {
let j = self.log[r - l + 1];
let left = self.table[j][l];
let right = self.table[j][r - (1 << j) + 1];
if arr[left] <= arr[right] {
left
} else {
right
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rmq() {
let arr = [1, 3, 2, 7, 9, 11];
let st = SparseTable::new(&arr);
assert_eq!(st.query(&arr, 0, 2), 0);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rmq() {
let arr = [1, 3, 2, 7, 9, 11];
let st = SparseTable::new(&arr);
assert_eq!(st.query(&arr, 0, 2), 0);
}
}
Deep Comparison
OCaml vs Rust: Range Minimum Query
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
range_gcd_query(arr, l, r) -> u64 using a sparse table (GCD is idempotent: gcd(gcd(a,b), gcd(b,c)) = gcd(a,b,c)).SparseTable that returns the actual value rather than the index, and compare its ergonomics for the min/max use case.