Range Minimum Query Method 2 and 3

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:

  1. Divide the range [0, n-1] into different blocks of √n each.
  2. 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:

  1. 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.