RMQ on static 2D array in constant time

675 Views Asked by At

The input is an array (n*m 1<n,m< 1000). I have to find the maximum element in every sub-matrice of size( a*b ).

I was trying to do this by iterating x over n-a+1 and y over m-j+1.

  • 2D segment trees or quad trees are not sufficiently fast as the number of queries is large.
  • I tried to extend sparse table but was not able to due to shortage of space.

  • I have read about solutions with Cartesian trees but some code is needed as I cannot understand it.

Please explain a solution that will answer a query in O(nm) time or in O(1) time by pre-computation. Also, the input array is static.

Note: although I've tried sparse table, it might not have been correct, so feel free to post a solution with it.

I'm a Java coder, so an implementation in Java or c/c++ would be great.

Also this is not a duplicate as I have searched a lot about it without finding anything suitable.

2

There are 2 best solutions below

0
On BEST ANSWER

Number of possibilities:

(From Number of submatrix of size AxB in a matrix of size MxN)
In a matrix of size (m*n), there are (n-A+1)*(m-B+1) different matrices of size (A*B).
So the total number of possible input for your function is sum((n-A+1)*(m-B+1)) where A=1..n and B=1..m.

EDIT: This is getting so huge when m=1000.

O(m^2n^2) is O(1000^4)... 1 trillion ... this won't fit in my small computer's memory :)

Structure:

I propose you build an hashmap that you simply index with the boundaries of your matrix:
A string built from a=M(i,j), b=M(k,l) where 0< i < k <(n+1) and 0< j < l <(m+1)

  • e.g. you can build is so: aHashMap("["+i+","+j+"].["+k+","+l+"]")

Pre-computation:

  • Have a function that calculates the max of a given matrix (a[i,j],b[k,l]) - say myMax(i,j,k,l). I assume there is no point showing you how.

  • Then its easy (sorry I can't easily compile anything so I give only the principle for now):

    for i=1 to n-1 do
        for j=1 to m-1 do 
            for k=i to n do
                for l=j to m do 
                    aHashMap("["+i+","+j+"].["+k+","+l+"]") = myMax(i,j,k,l)
                next
            next
        next
    next
    

This is O(n^4), but assuming its pre-computational, there's no point, it just make your program bigger and bigger when storing aHashMap.

Good to know

Such problem also seem to be widely addressed at http://cs.stackexchange.com ; e.g. this or that... so this SE could be of interest to the OP.

Implementation of this naive approach:

With 99 x 95 it already gives millions of possibilities to pre-compute, taking about 3Go RAM!

$ ./p1
 enter number of rows:99
 enter number of cols:95
pre-computing...
hashmap ready with 22572000 entries.

matrix.h

#ifndef myMatrix_JCHO
#define myMatrix_JCHO

typedef unsigned int u_it;

class Matrix
{
public:
    Matrix(u_it _n, u_it _m);
    Matrix(const Matrix& matr);
    Matrix& operator=(const Matrix& matr);
    ~Matrix();

    u_it getNumRows() ;
    u_it getNumCols() ;

    int getC(u_it i, u_it j);
    void setC(u_it i, u_it j, int v);
    void printMatrix();
    int maxSub(u_it a_i, u_it a_j, u_it b_k, u_it b_l);

private:
    u_it n, m;
    int **pC;

};

#endif

matrix.cpp

#include <iostream>
#include <string>
#include <sstream>

#include "matrix.h"

Matrix::Matrix(u_it _n, u_it _m) {
    n=_n;
    m=_m;
    int k=0;
    pC = new int*[n];
    for (u_it i=0; i<n; ++i){
       pC[i]=new int[m];
       for(u_it j=0; j<m; ++j){
         pC[i][j]=++k;
       }
    }
}
Matrix::~Matrix(){
    for (u_it i=0; i<n; ++i){
        delete [] pC[i];
    }
    delete [] pC;
    std::cout << "matrix destroyed\n";
}
u_it Matrix::getNumRows() {
    return n;
}
u_it Matrix::getNumCols() {
    return m;
}
int Matrix::getC(u_it i, u_it j){
    return pC[i][j];
}
void Matrix::setC(u_it i, u_it j, int v){
    pC[i][j]=v;
}
void Matrix::printMatrix(){
    for (u_it i=0; i<n; ++i){
      std::cout << "row " <<i<<" [ ";
      for(u_it j=0; j<m; ++j){
        std::cout << pC[i][j] << '\t';
      }
      std::cout << "]\n";
    }
}

// Return max of submatrix a(i,j); b(k,l)
int Matrix::maxSub(u_it a_i, u_it a_j, u_it b_k, u_it b_l) {
   int res = -100000;
   if (a_i<=b_k && a_j<=b_l && b_k<n && b_l<m) {
     for (u_it i=a_i; i<=b_k; ++i){
       for(u_it j=a_j; j<=b_l; ++j){
         res= (pC[i][j]>res)? pC[i][j] : res;
       }
     }
   } else {
     std::cout << "invalid arguments: out of bounds\n";
     return -100000;
   }

   return res;
}

main.cpp

#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <cassert>

#include "matrix.h"

std::string hashKey(u_it a_i, u_it a_j, u_it b_k, u_it b_l) {
    std::stringstream ss;
    ss << "max(a[" << a_i << "," << a_j << "],b[" << b_k << "," << b_l << "]";
    return ss.str();
}

int main() {
    u_it n_rows, n_cols;
    std::cout << " enter number of rows:";
    std::cin >> n_rows;
    std::cout << " enter number of cols:";
    std::cin >> n_cols;
    std::cout << " pre-computing...\n";

    std::map<std::string, int> myHMap;
    Matrix * mat=new Matrix(n_rows,n_cols);
    //mat->printMatrix();
    // "PRE" computation
    for (u_it i=0; i<n_rows; ++i) {
      for (u_it j=0; j<n_cols; ++j) {
        for (u_it k=i; k<n_rows; ++k) {
          for (u_it l=j; l<n_cols; ++l) {
            //std::cout <<"max(a["<<i<<","<<j<<"],b["<<k<<","<<l<<"]"<< mat->maxSub(i, j, k, l) <<'\n';
            //std::cout << mat->hashKey(i, j, k ,l) <<" -> " <<  mat->maxSub(i, j, k, l)  <<'\n';
            myHMap[hashKey(i, j, k ,l)] = mat->maxSub(i, j, k, l);
          }
        }
      }
    }
    std::cout << " hashmap ready with "<<  myHMap.size() <<" entries.\n";
    // call to values
    u_it cw_i, cw_j, cw_k, cw_l;
    cw_i=0;
    std::string hKey;
    while (cw_i < n_rows+1) {
      std::cout << " enter i,:";
      std::cin >> cw_i;
      std::cout << " enter j,:";
      std::cin >> cw_j;
      std::cout << " enter k,:";
      std::cin >> cw_k;
      std::cout << " enter l:";
      std::cin >> cw_l;
      hKey = hashKey(cw_i, cw_j, cw_k, cw_l);
      std::map<std::string, int>::iterator i = myHMap.find(hKey);
      assert(i != myHMap.end());
      std::cout << i->first <<" -> " <<  i->second  <<'\n';
    }
}

make

g++ -std=c++0x  -std=c++0x -Wall -c -g matrix.cpp
g++ -std=c++0x  -std=c++0x -Wall -c -g main.cpp
g++ -std=c++0x  -std=c++0x -Wall -g matrix.o main.o -o p1
0
On

I found the answer to have it compute in O(mn) - still with a little pre-computation - but this time that last one is easily O(mn.log(mn)) too: its just ordering a list of all the matrix's values.

Pre-compute:

First step is to simply build an orderly structure of the matrix's values, say M(A), then use <algorithm>std::sort to order that structure.

Retreive max of any sub-matrix (a,b):

To get the max of any matrix, just start with the biggest from pre-computed structure M(A) and check if it is within (a,b).

  • If it is, then you're done
  • Else, take the next one in M(A)

matrix.h

#ifndef myMatrix_JCHO
#define myMatrix_JCHO

typedef unsigned int u_it;
typedef std::pair<u_it, u_it> uup;

class Matrix
{
public:
    Matrix(u_it _n, u_it _m);
    Matrix(const Matrix& matr);
    Matrix& operator=(const Matrix& matr);
    ~Matrix();

    u_it getNumRows() ;
    u_it getNumCols() ;

    int getC(u_it i, u_it j);
    void setC(u_it i, u_it j, int v);
    void printMatrix();
    int maxSub(u_it a_i, u_it a_j, u_it b_k, u_it b_l);

private:
    u_it n, m;
    int **pC;

};

#endif

matrix.cpp

#include <iostream>
#include <string>
#include <sstream>

#include "matrix.h"

Matrix::Matrix(u_it _n, u_it _m) {
    n=_n;
    m=_m;
    //int k=0;
    pC = new int*[n];
    for (u_it i=0; i<n; ++i){
       pC[i]=new int[m];
       for(u_it j=0; j<m; ++j){
         pC[i][j]=rand()%1000;
       }
    }
}
Matrix::~Matrix(){
    for (u_it i=0; i<n; ++i){
        delete [] pC[i];
    }
    delete [] pC;
    std::cout << "matrix destroyed\n";
}
u_it Matrix::getNumRows() {
    return n;
}
u_it Matrix::getNumCols() {
    return m;
}
int Matrix::getC(u_it i, u_it j){
    return pC[i][j];
}
void Matrix::setC(u_it i, u_it j, int v){
    pC[i][j]=v;
}
void Matrix::printMatrix(){
    for (u_it i=0; i<n; ++i){
      std::cout << "row " <<i<<" [ ";
      for(u_it j=0; j<m; ++j){
        std::cout << pC[i][j] << '\t';
      }
      std::cout << "]\n";
    }
}

main.cpp

#include <iostream>
#include <string>
#include <utility>
#include <algorithm>
#include <vector>

#include "matrix.h"


// sort function for my vector of pair:
bool oMyV(std::pair<uup, int> x, std::pair<uup, int> y) { return (x.second > y.second); }

// check that p is within matrix formed by a and b
bool isIn_a_b(uup p, uup a, uup b){
    bool res = false;
    if (p.first >= a.first && p.first <= b.first) {
      if (p.second >= a.second && p.second <= b.second) {
        res = true;
      }
    }
    return res;
}

int main() {
    u_it n_rows, n_cols;
    std::cout << " enter number of rows:";
    std::cin >> n_rows;
    std::cout << " enter number of cols:";
    std::cin >> n_cols;
    std::cout << " pre-computing...\n";

    std::pair<uup, int> *ps;
    std::vector<std::pair<uup, int> > myV;
    Matrix * mat=new Matrix(n_rows,n_cols);
    // print to debug:
    mat->printMatrix();
    // "PRE" computation
    for (u_it i=0; i<n_rows; ++i) {
      for (u_it j=0; j<n_cols; ++j) {
        ps=new std::pair<uup, int>(std::make_pair(i,j), mat->getC(i,j));
        myV.push_back(*ps);
      }
    }
    std::sort(myV.begin(), myV.end(), oMyV);
    /* in case you want to print ordered valuet ordered valuess for debug */
    for (std::vector<std::pair<uup, int> >::iterator it=myV.begin(); it!=myV.end(); ++it) {
      std::cout << it->second << " at [" << it->first.first <<','<<it->first.second<< "]\n";
    }
    /**/

    // call to values
    bool byebye=false;
    uup a, b;

    do {
      std::cout << " enter i,:";     std::cin >> a.first;
      std::cout << " enter j,:";     std::cin >> a.second;
      std::cout << " enter k,:";     std::cin >> b.first;
      std::cout << " enter l,:";     std::cin >> b.second;

      std::vector<std::pair<uup, int> >::iterator it=myV.begin();
      std::cout << " a:["<<a.first<<','<<a.second<<"]-b:["<<b.first<<','<<b.second<<"] in ";
      std::cout << " M:[0,0]--:["<<n_rows-1<<','<<n_cols-1<<"]\n";
      // check validity:
      if (  isIn_a_b(a, std::make_pair(0,0), std::make_pair(n_rows-1, n_cols-1) )
         && isIn_a_b(b, std::make_pair(0,0), std::make_pair(n_rows-1, n_cols-1) )
         && (a.first <= b.first)
         && (a.second <= b.second)
         ) {
        while (! isIn_a_b(it->first, a, b) && it!=myV.end()){
          ++it;
        }
        std::cout << "Found:" << it->second << " at [" << it->first.first <<','<<it->first.second<< "]\n";
      } else {
        std::cout << "makes no sense. bye.\n";
        byebye=true;
      }
    } while (!byebye);
}

Makefile

(don't forget: tabulate in Makefile)

OBJS = matrix.o main.o
CC = g++ -std=c++0x
DEBUG = -g
CFLAGS = -std=c++0x -Wall -c $(DEBUG)
LFLAGS = -std=c++0x -Wall $(DEBUG)
TARFILE =  ${HOME}/jcho/good/matrix.tar

p1 : $(OBJS)
        $(CC) $(LFLAGS) $(OBJS) -o p1

matrix.o: matrix.cpp matrix.h
        $(CC) $(CFLAGS) matrix.cpp

main.o: main.cpp matrix.h
        $(CC) $(CFLAGS) main.cpp

clean:
        \rm -f *.o *~ p1

tar:
        tar cfv  $(TARFILE) *.h *.cpp Makefile \
            p1 && \
            echo "tar $(TARFILE) created successfuly."