4
4
*
5
5
* <p>Main inspiration: https://cp-algorithms.com/data_structures/sparse-table.html
6
6
*
7
+ * <p>To run this file:
8
+ *
9
+ * <p>./gradlew run -Pmain=com.williamfiset.algorithms.datastructures.sparsetable.SparseTable
10
+ *
7
11
* @author William Fiset, [email protected]
8
12
*/
9
13
package com .williamfiset .algorithms .datastructures .sparsetable ;
@@ -22,7 +26,7 @@ public class SparseTable {
22
26
private int [] log2 ;
23
27
24
28
// The sprase table values.
25
- private long [][] t ;
29
+ private long [][] dp ;
26
30
27
31
// Index Table (IT) associated with the values in the sparse table.
28
32
private int [][] it ;
@@ -53,18 +57,19 @@ public enum Operation {
53
57
};
54
58
55
59
public SparseTable (long [] values , Operation op ) {
60
+ // TODO(william): Lazily call init in query methods instead of initializing in constructor?
56
61
this .op = op ;
57
62
init (values );
58
63
}
59
64
60
65
private void init (long [] v ) {
61
66
n = v .length ;
62
67
P = (int ) (Math .log (n ) / Math .log (2 ));
63
- t = new long [P + 1 ][n ];
68
+ dp = new long [P + 1 ][n ];
64
69
it = new int [P + 1 ][n ];
65
70
66
71
for (int i = 0 ; i < n ; i ++) {
67
- t [0 ][i ] = v [i ];
72
+ dp [0 ][i ] = v [i ];
68
73
it [0 ][i ] = i ;
69
74
}
70
75
@@ -76,27 +81,28 @@ private void init(long[] v) {
76
81
// Build sparse table combining the values of the previous intervals.
77
82
for (int p = 1 ; p <= P ; p ++) {
78
83
for (int i = 0 ; i + (1 << p ) <= n ; i ++) {
79
- long leftInterval = t [p - 1 ][i ], rightInterval = t [p - 1 ][i + (1 << (p - 1 ))];
84
+ long leftInterval = dp [p - 1 ][i ];
85
+ long rightInterval = dp [p - 1 ][i + (1 << (p - 1 ))];
80
86
if (op == Operation .MIN ) {
81
- t [p ][i ] = minFn .apply (leftInterval , rightInterval );
87
+ dp [p ][i ] = minFn .apply (leftInterval , rightInterval );
82
88
// Propagate the index of the best value
83
89
if (leftInterval <= rightInterval ) {
84
90
it [p ][i ] = it [p - 1 ][i ];
85
91
} else {
86
92
it [p ][i ] = it [p - 1 ][i + (1 << (p - 1 ))];
87
93
}
88
94
} else if (op == Operation .MAX ) {
89
- t [p ][i ] = maxFn .apply (leftInterval , rightInterval );
95
+ dp [p ][i ] = maxFn .apply (leftInterval , rightInterval );
90
96
// Propagate the index of the best value
91
97
if (leftInterval >= rightInterval ) {
92
98
it [p ][i ] = it [p - 1 ][i ];
93
99
} else {
94
100
it [p ][i ] = it [p - 1 ][i + (1 << (p - 1 ))];
95
101
}
96
102
} else if (op == Operation .SUM ) {
97
- t [p ][i ] = sumFn .apply (leftInterval , rightInterval );
103
+ dp [p ][i ] = sumFn .apply (leftInterval , rightInterval );
98
104
} else if (op == Operation .GCD ) {
99
- t [p ][i ] = gcdFn .apply (leftInterval , rightInterval );
105
+ dp [p ][i ] = gcdFn .apply (leftInterval , rightInterval );
100
106
}
101
107
}
102
108
}
@@ -127,8 +133,8 @@ public int queryIndex(int l, int r) {
127
133
private int minQueryIndex (int l , int r ) {
128
134
int len = r - l + 1 ;
129
135
int p = log2 [r - l + 1 ];
130
- long leftInterval = t [p ][l ];
131
- long rightInterval = t [p ][r - (1 << p ) + 1 ];
136
+ long leftInterval = dp [p ][l ];
137
+ long rightInterval = dp [p ][r - (1 << p ) + 1 ];
132
138
if (leftInterval <= rightInterval ) {
133
139
return it [p ][l ];
134
140
} else {
@@ -139,8 +145,8 @@ private int minQueryIndex(int l, int r) {
139
145
private int maxQueryIndex (int l , int r ) {
140
146
int len = r - l + 1 ;
141
147
int p = log2 [r - l + 1 ];
142
- long leftInterval = t [p ][l ];
143
- long rightInterval = t [p ][r - (1 << p ) + 1 ];
148
+ long leftInterval = dp [p ][l ];
149
+ long rightInterval = dp [p ][r - (1 << p ) + 1 ];
144
150
if (leftInterval >= rightInterval ) {
145
151
return it [p ][l ];
146
152
} else {
@@ -153,14 +159,16 @@ private int maxQueryIndex(int l, int r) {
153
159
// Perform a cascading query which shrinks the left endpoint while summing over all the intervals
154
160
// which are powers of 2 between [l, r].
155
161
//
156
- // NOTE: You can achieve a faster time complexity with less memory using a simple prefix array...
157
162
// WARNING: This method can easily produces values that overflow.
163
+ //
164
+ // NOTE: You can achieve a faster time complexity and use less memory with a simple prefix sum
165
+ // array. This method is here more as a proof of concept than for its usefulness.
158
166
private long sumQuery (int l , int r ) {
159
167
long sum = 0 ;
160
168
for (int p = P ; p >= 0 ; p --) {
161
169
int rangeLength = r - l + 1 ;
162
170
if ((1 << p ) <= rangeLength ) {
163
- sum += t [p ][l ];
171
+ sum += dp [p ][l ];
164
172
l += (1 << p );
165
173
}
166
174
}
@@ -173,55 +181,26 @@ private long sumQuery(int l, int r) {
173
181
// which we'll call k. Then we can query the intervals [l, l+k] and [r-k+1, r] (which likely
174
182
// overlap) and apply the function again. Some functions (like min and max) don't care about
175
183
// overlapping intervals so this trick works, but for a function like sum this would return the
176
- // wrong result.
184
+ // wrong result since it is not an idempotent binary function .
177
185
private long query (int l , int r , BinaryOperator <Long > fn ) {
178
186
int len = r - l + 1 ;
179
187
int p = log2 [r - l + 1 ];
180
- return fn .apply (t [p ][l ], t [p ][r - (1 << p ) + 1 ]);
188
+ return fn .apply (dp [p ][l ], dp [p ][r - (1 << p ) + 1 ]);
181
189
}
182
190
191
+ /* Example usage: */
192
+
183
193
public static void main (String [] args ) {
184
- long [] v = {2 , -3 , 4 , 1 , 0 , -1 , 5 , 6 };
185
- SparseTable st = new SparseTable (v , Operation .MIN );
186
- System .out .println (st .query (2 , 7 ));
187
- System .out .println (st .queryIndex (2 , 7 ));
194
+ long [] values = {2 , -3 , 4 , 1 , 0 , -1 , -1 , 5 , 6 };
188
195
189
- simpleTest ();
190
- simpleMinQueryTest ();
191
- }
196
+ // Initialize sparse table to do range minimum queries.
197
+ SparseTable sparseTable = new SparseTable (values , Operation .MIN );
192
198
193
- private static void simpleMinQueryTest () {
194
- long [] v = {2 , -3 , 4 , 1 , 0 , -1 , 5 , 6 };
195
- SparseTable st = new SparseTable (v , Operation .MIN );
196
- for (int i = 0 ; i < v .length ; i ++) {
197
- for (int j = i ; j < v .length ; j ++) {
198
- long m = Long .MAX_VALUE ;
199
- for (int k = i ; k <= j ; k ++) {
200
- m = Math .min (m , v [k ]);
201
- }
202
- if (st .query (i , j ) != m ) {
203
- System .out .println ("Sparse table value incorrect." );
204
- }
205
- if (v [st .queryIndex (i , j )] != m ) {
206
- System .out .println ("SparseTable index incorrect. Got index: " + st .queryIndex (i , j ));
207
- }
208
- }
209
- }
210
- }
199
+ // Prints: "Min value between [2, 7] = -1"
200
+ System .out .printf ("Min value between [2, 7] = %d\n " , sparseTable .query (2 , 7 ));
211
201
212
- private static void simpleTest () {
213
- long [] v = {2 , -3 , 4 , 1 , 0 , -1 , 5 , 6 };
214
- SparseTable st = new SparseTable (v , Operation .SUM );
215
- for (int i = 0 ; i < v .length ; i ++) {
216
- for (int j = i ; j < v .length ; j ++) {
217
- long trueSum = 0 ;
218
- for (int k = i ; k <= j ; k ++) {
219
- trueSum += v [k ];
220
- }
221
- if (st .query (i , j ) != trueSum ) {
222
- System .out .printf ("Ooopse, got %d instead of %d!\n " , st .query (i , j ), trueSum );
223
- }
224
- }
225
- }
202
+ // Prints: "Index of min value between [2, 7] = 5". Returns the leftmost index in the
203
+ // event that there are duplicates.
204
+ System .out .printf ("Index of min value between [2, 7] = %d\n " , sparseTable .queryIndex (2 , 7 ));
226
205
}
227
206
}
0 commit comments