Hello Everyone,
I hope you have gone through the “ Range Minimum Query (Square Root Decomposition and Sparse Table)” before coming here. It’s really advisable to completely read part 1, then only continue with part 2.
Link: Range Minimum Query
Method 2 (Square Root Decomposition)
We can use Square Root Decompositions to reduce space required in the above method.
Preprocessing:
- Divide the range [0, n-1] into different blocks of √n each.
- Compute the minimum of every block of size √n and store the results.
Preprocessing takes O(√n * √n) = O(n) time and O(√n) space.
Query:
- To query a range [L, R], we take a minimum of all blocks that lie in this range. For left and right corner blocks which may partially overlap with the given range, we linearly scan them to find the minimum.
The time complexity of the query is O(√n) . Note that we have a minimum of the middle block directly accessible and there can be at most O(√n) middle blocks. There can be at most two corner blocks that we may have to scan, so we may have to scan 2*O(√n) elements of corner blocks. Therefore, the overall time complexity is O(√n) .
Method 3 (Sparse Table Algorithm)
The above solution requires only O(√n) space but takes O(√n) time to query. The sparse table method supports query time O(1) with extra space O(n Log n) .
The idea is to precompute a minimum of all subarrays of size 2j where j varies from 0 to Log n . Like method 1, we make a lookup table. Here lookup[i][j] contains a minimum of range starting from i and of size 2j. For example lookup[0][3] contains a minimum of range [0, 7] (starting with 0 and of size 23)
Preprocessing:
How to fill this lookup table? The idea is simple, fill in a bottom-up manner using previously computed values.
For example, to find a minimum of range [0, 7], we can use a minimum of the following two.
a) Minimum of range [0, 3]
b) Minimum of range [4, 7]
Based on the above example, below is the formula,
// If arr[lookup[0][2]] <= arr[lookup[4][2]], // then lookup[0][3] = lookup[0][2] If arr[lookup[i][j-1]] <= arr[lookup[i+2j-1][j-1]] lookup[i][j] = lookup[i][j-1] // If arr[lookup[0][2]] > arr[lookup[4][2]], // then lookup[0][3] = lookup[4][2] Else lookup[i][j] = lookup[i+2j-1][j-1]
Query:
For any arbitrary range [l, R], we need to use ranges that are in powers of 2. The idea is to use the closest power of 2. We always need to do at most one comparison (compare a minimum of two ranges which are powers of 2). One range starts with L and ends with “L + closest-power-of-2”. The other range ends at R and starts with “R – same-closest-power-of-2 + 1”. For example, if the given range is (2, 10), we compare a minimum of two ranges (2, 9) and (3, 10).
Based on the above example, below is the formula,
// For (2,10), j = floor(Log2(10-2+1)) = 3 j = floor(Log(R-L+1)) // If arr[lookup[0][3]] <= arr[lookup[3][3]], // then RMQ(2,10) = lookup[0][3] If arr[lookup[L][j]] <= arr[lookup[R-(int)pow(2,j)+1][j]] RMQ(L, R) = lookup[L][j] // If arr[lookup[0][3]] > arr[lookup[3][3]], // then RMQ(2,10) = lookup[3][3] Else RMQ(L, R) = lookup[R-(int)pow(2,j)+1][j]
Since we do only one comparison, the time complexity of the query is O(1).
Below is the implementation of the above idea.
// C++ program to do range minimum
// query in O(1) time with
// O(n Log n) extra space and
// O(n Log n) preprocessing time
#include <bits/stdc++.h>
using
namespace
std;
#define MAX 500
// lookup[i][j] is going to
// store index of minimum value in
// arr[i..j]. Ideally lookup
// table size should not be fixed
// and should be determined using
// n Log n. It is kept
// constant to keep code simple.
int
lookup[MAX][MAX];
// Structure to represent a query range
struct
Query {
int
L, R;
};
// Fills lookup array
// lookup[][] in bottom up manner.
void
preprocess(
int
arr[],
int
n)
{
// Initialize M for the
// intervals with length 1
for
(
int
i = 0; i < n; i++)
lookup[i][0] = i;
// Compute values from smaller
// to bigger intervals
for
(
int
j = 1; (1 << j) <= n; j++)
{
// Compute minimum value for
// all intervals with size
// 2^j
for
(
int
i = 0; (i + (1 << j) - 1) < n; i++)
{
// For arr[2][10], we
// compare arr[lookup[0][3]]
// and arr[lookup[3][3]]
if
(arr[lookup[i][j - 1]]
< arr[lookup[i + (1 << (j - 1))][j - 1]])
lookup[i][j] = lookup[i][j - 1];
else
lookup[i][j]
= lookup[i + (1 << (j - 1))][j - 1];
}
}
}
// Returns minimum of arr[L..R]
int
query(
int
arr[],
int
L,
int
R)
{
// For [2,10], j = 3
int
j = (
int
)log2(R - L + 1);
// For [2,10], we compare arr[lookup[0][3]] and
// arr[lookup[3][3]],
if
(arr[lookup[L][j]]
<= arr[lookup[R - (1 << j) + 1][j]])
return
arr[lookup[L][j]];
else
return
arr[lookup[R - (1 << j) + 1][j]];
}
// Prints minimum of given
// m query ranges in arr[0..n-1]
void
RMQ(
int
arr[],
int
n, Query q[],
int
m)
{
// Fills table lookup[n][Log n]
preprocess(arr, n);
// One by one compute sum of all queries
for
(
int
i = 0; i < m; i++)
{
// Left and right boundaries
// of current range
int
L = q[i].L, R = q[i].R;
// Print sum of current query range
cout <<
"Minimum of ["
<< L <<
", "
<< R <<
"] is "
<< query(arr, L, R) << endl;
}
}
// Driver code
int
main()
{
int
a[] = { 7, 2, 3, 0, 5, 10, 3, 12, 18 };
int
n =
sizeof
(a) /
sizeof
(a[0]);
Query q[] = { { 0, 4 }, { 4, 7 }, { 7, 8 } };
int
m =
sizeof
(q) /
sizeof
(q[0]);
RMQ(a, n, q, m);
return
0;
}
Output
Minimum of [0, 4] is 0 Minimum of [4, 7] is 3 Minimum of [7, 8] is 12
So sparse table method supports query operation in O(1) time with O(n Log n) preprocessing time and O(n Log n) space.