RMQ of A(i,j) – returns the index of the smallest element in the subarray A[i...j]
Given the Array E below, RMQ of E(2,7) is 3.
LCA of Tree (u,v) – given nodes u,v in Tree, returns the node furthest from the root that is an ancestor of both u and v.
Given the tree below, LCA("5", "9") is node "4".
Notation for the overall complexity of an algorithm <f(n), g(n)>.
Here we can reduce LCA problem to RMQ: If there is <f(n), g(n)> solution for RMQ, then there is an <f(2n-1)+O(n), g(2n-1)+O(1)> solution for LCA.
The Euler tour of a tree is the path through the tree that begins at the root and
ends at the root, traversing each edge exactly twice : once to enter the subtree, and once to exit
it. The Euler tour of a tree is essentially the depth-first traversal of a tree that returns to the root
at the end. The correspondence between a tree and its Euler tour is shown in the Figure above. Euler tour size is 2n-1 for a tree with n node.
The LCA of nodes u and v is the shallowest node encountered between the visits to u and to v during a depth first search traversal (Euler tour) of Tree. Well, otherwise it will contradiction to the DFS tour.
On an input tree T with n nodes, we build 3 arrays.
Euler array E[1,..,2n-1] – The nodes visited in an Euler tour of T. E[i] is the label of the i-th node visited in the tour.
Level array L[1,..2n-1] – The level of the nodes we got in the tour. L[i] is the level of node E[i]. (level is defined to be the distance from the root)
Representative array R[1,..n] – R[i] will hold the index of the first occurrence of node i in E[i].
R[v] = minimum of i where E[i] = v.
To get LCA of Tree(x,y):
All nodes in the Euler tour between the first visits to x and y are E[R[x],..,R[y]] assume that R[x] < R[y]. The shallowest node in the sub-tour is at index RMQ of L(R[x],R[y]), since L[i] stores the level of the node at E[i]. RMQ will return the index, thus we output the node at E[RMQ of L(R[x],R[y])] as LCA of Tree(x,y).
Preprocessing Complexity: L,R,E – Each is built in O(n) time, during the DFS run. Preprocessing L for RMQ is f(2n-1).
Query Complexity: RMQ query on L is g(2n-1) and array references is O(1)
Overall Complexity: <f(2n-1)+O(n), g(2n-1)+O(1)>.
Solution One - Dynamic Programming <O(n^2), O(1)>:
Overall Complexity:
Here is the code implementation:
Solution Two - Sparse Table <O(n log(n), O(1)>:
Overall Complexity:
Here is the code implementation:
Overall Complexity: <O(n), O(1)>
Here is the code implementation:
That is it, folks. Enjoy the journey.
The LCA of nodes u and v is the shallowest node encountered between the visits to u and to v during a depth first search traversal (Euler tour) of Tree. Well, otherwise it will contradiction to the DFS tour.
On an input tree T with n nodes, we build 3 arrays.
Euler array E[1,..,2n-1] – The nodes visited in an Euler tour of T. E[i] is the label of the i-th node visited in the tour.
Level array L[1,..2n-1] – The level of the nodes we got in the tour. L[i] is the level of node E[i]. (level is defined to be the distance from the root)
Representative array R[1,..n] – R[i] will hold the index of the first occurrence of node i in E[i].
R[v] = minimum of i where E[i] = v.
To get LCA of Tree(x,y):
All nodes in the Euler tour between the first visits to x and y are E[R[x],..,R[y]] assume that R[x] < R[y]. The shallowest node in the sub-tour is at index RMQ of L(R[x],R[y]), since L[i] stores the level of the node at E[i]. RMQ will return the index, thus we output the node at E[RMQ of L(R[x],R[y])] as LCA of Tree(x,y).
Preprocessing Complexity: L,R,E – Each is built in O(n) time, during the DFS run. Preprocessing L for RMQ is f(2n-1).
Query Complexity: RMQ query on L is g(2n-1) and array references is O(1)
Overall Complexity: <f(2n-1)+O(n), g(2n-1)+O(1)>.
Solution One - Dynamic Programming <O(n^2), O(1)>:
Overall Complexity:
Here is the code implementation:
static private class FindRMQWithDynamicProgramming<T> implements FindRMQInterface <T> { private int[] A; private int[][] M; private int N; public FindRMQWithDynamicProgramming( int[] L) { super(); this.A = L; this.N = L.length; this.M = new int[N][N]; process(this.M, this.A, this.N); } /** * * Dynamic Programming * * M[i][j] = M[i][j - 1] if A[M[i][j - 1]] < A[j] * or M[i][j] = j * @param M * @param A * @param N */ private void process(int[][] M, int[] A, int N) { int i, j; for (i =0; i < N; i++) M[i][i] = i; for (i = 0; i < N; i++) for (j = i + 1; j < N; j++) if (A[M[i][j - 1]] < A[j]) M[i][j] = M[i][j - 1]; else M[i][j] = j; } public int rmq(int i, int j) { return this.M[i][j]; } }
Solution Two - Sparse Table <O(n log(n), O(1)>:
With dynamic programming, the table M can be built in O( n log(n) ) time.
To get M[i,j] for arbitrary i and j, select two blocks that entirely cover the subrange [i...j], and let k = (int) log(j - i + 1), so that 2^k is the largest block that fits [i...j].
Overall Complexity:
Here is the code implementation:
static private class FindRMQWithSparseTable<T> implements FindRMQInterface <T> { private int[] A; private int[][] M; private int N; public FindRMQWithSparseTable( int[] L ) { super(); this.A = L; this.N = L.length; this.M = new int[N][N]; process(this.M, this.A, this.N); } private void process(int[][] M, int[] A, int N) { // initialize M for the intervals of length one for (int i = 0; i < N; i++) M[i][0] = i; // compute values from smaller to bigger for (int j = 1; 1 << j <= N; j++) { for (int i = 0; i + (1 << j) - 1 < N; i++) { if (A[M[i][j - 1]] < A[M[i + (1 << (j - 1))][j - 1]]) M[i][j] = M[i][j - 1]; else M[i][j] = M[i + (1 << (j - 1))][j - 1]; } } } public int rmq(int i, int j) { int k = (int) Math.log(j - i + 1); if (A[M[i][k]] <= A[M[j - (1 << k) + 1][k]]) return M[i][k]; else return M[j - (1 << k) + 1][k]; } }
Solution Three - <O(n), O(1)> algorithm for restricted RMQ.
Overall Complexity: <O(n), O(1)>
Here is the code implementation:
static private class FindRestrictedRMQWithSparseTable<T> implements FindRMQInterface <T> { private int[] A; private int[] B; private int[] T; private int [] Bl; private int N; private int blockSize; //private int blockNum; private FindRMQWithSparseTable<T> blockRMQSparseTable = null; // construct the binary blocks private int [] buildBlockArray(int[] L, int b, int n) { // round size of B to n blocks with size b int [] blocks = new int[(n*b)]; blocks[0] = L[0]; for (int i=1; i<L.length; i++) { blocks[i] = L[i] - L[i-1]; if ( blocks[i]<0 ) blocks[i] = 0; } for (int i=L.length; i<blocks.length; i++) { blocks[i] = blocks[i-1] + 1; } return blocks; } public FindRestrictedRMQWithSparseTable(int [] L) { super(); this.A = L; this.N = L.length; int b = (int) Math.ceil ( ( Math.log(N)/Math.log(2) ) / (double) 2 ); this.blockSize = b; int n = (int) Math.ceil ( (double) N / (double) b ) ; //this.blockNum = n; // build the binary array this.B = buildBlockArray(L, b, n); // build the lookup table this.T = buildLookupTable(this.B, b, n); buildMinIndexLookupTable(b, n); // System.out.println("Bl=" + Arrays.toString(Bl)); // System.out.println("T=" + Arrays.toString(T)); } private void buildMinIndexLookupTable(int b, int n) { this.Bl = new int[ n ] ; int [] blT = new int[ n ] ; for (int i=0; i<n; i++) { int idx = getIndex(B, i*b, b); int s = ( idx * ( (b) * (b+1) /2 ) ); Bl[i] = i*b + T[s+2]; blT[i] = A[Bl[i]]; } blockRMQSparseTable = new FindRMQWithSparseTable<T> (blT); } // construct the lookup table private int [] buildLookupTable(int [] blocks, int b, int n) { int sizeT = (int) Math.pow(2, b) * ( (b) * (b+1) /2 ); int [] table = new int[ sizeT ] ; for (int k=0; k<table.length; k++) { table[k]=-1; } for (int i=0; i<n; i++) { for (int j=0; j<b; j++) { int idx = getIndex(blocks, i*b, b); int [][] m = preProcess(blocks, i*b, b); int s = ( idx * ( (b) * (b+1) /2 ) ); if ( table[s] < 0 ) { for (int k=0; k<b; k++) { for (int l=k; l<b; l++) { table[ s ] = m[k][l]; s++; } } } } } return table; } private int [][] preProcess(int[] A, int start, int n) { int [][] matrix = new int[n][n]; int i, j; for (i =0; i<n; i++) matrix[i][i] = i; for (i = 0; i < n; i++) { for (j = i + 1; j < n; j++) { int a = A[ start + matrix[i][j - 1]]; int b = A[ start + j]; if ( a < b ) { matrix[i][j] = matrix[i][j - 1]; } else if ( a > b ) { matrix[i][j] = j; } else if ( a == b ) { matrix[i][j] = j; } } } return matrix; } private int getIndex(int[] A, int start, int len) { int idx = 0; for (int i = start; i<start+len; i++) { idx = idx * 2; idx += A[i]; } return idx; } public int rmq(int i, int j) { int i1 = (int) ( (double) i / (double) this.blockSize ) ; int j1 = (int) Math.ceil ( (double) i / (double) this.blockSize ) ; int i2 = (int) ( (double) j / (double) this.blockSize ) ; int j2 = (int) Math.ceil ( (double) j / (double) this.blockSize ) ; int blockMinimum = -1; if ( (j1) < (i2) ) { blockMinimum = Bl[blockRMQSparseTable.rmq(j1, i2)]; } int idx1 = getIndex(B, i1*this.blockSize , this.blockSize); int idx2 = getIndex(B, i2*this.blockSize , this.blockSize); int s1 = ( idx1 * ( (this.blockSize) * (this.blockSize+1) /2 ) ); int s2 = ( idx2 * ( (this.blockSize) * (this.blockSize+1) /2 ) ); int jT1 = s1 + ( (this.blockSize) * (this.blockSize+1) /2 ) * ( i- i1*blockSize) + ( i2- i ) ; int iT2 = s2 + ( j- j2*blockSize) ; int firstBlockMinimum = i1 * this.blockSize + T[jT1]; int lastBlockMinimum = i2 * this.blockSize + T[iT2]; int[] indexes = new int[] {blockMinimum, firstBlockMinimum, lastBlockMinimum}; int index = findMinimumIndex( indexes ); return indexes[index]; } public int findMinimumIndex(int[] idx) { int minIndex = Integer.MAX_VALUE; for (int i=0; i<idx.length; i++) { if (idx[i]>=0) { if (A[i]<minIndex) minIndex = i; } } return minIndex; } }
That is it, folks. Enjoy the journey.