Last active
June 19, 2022 10:49
-
-
Save Phryxia/2cd9cb9aa8fc3c6cfa5f3a438263f1dc to your computer and use it in GitHub Desktop.
TypeScript implementation of Range Minimum Query (RMQ) using Sparse Table
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function getP(x: number): number { | |
return Math.floor(Math.log2(x)) | |
} | |
export interface ResultPair<T> { | |
value: T | |
index: number | |
} | |
export function rmq<T>( | |
itr: Iterable<T>, | |
op: (a: T, b: T) => { value: T; isFromA?: boolean }, | |
isOverlapping: boolean, | |
): (index0: number, index1: number) => ResultPair<T> { | |
const table = [] as ResultPair<T>[][] | |
let N = 0 | |
table[0] = [] | |
for (const x of itr) { | |
table[0][N] = { value: x, index: N } | |
N += 1 | |
} | |
const P = getP(N) | |
for (let p = 1; p <= P; ++p) { | |
table[p] = [] | |
const L = 2 ** (p - 1) | |
for (let n = 0; n + 2 * L - 1 < N; ++n) { | |
const { value, isFromA } = op( | |
table[p - 1][n].value, | |
table[p - 1][n + L].value, | |
) | |
table[p][n] = { | |
value, | |
index: isFromA ? table[p - 1][n].index : table[p - 1][n + L].index, | |
} | |
} | |
} | |
return function query(index0: number, index1: number): ResultPair<T> { | |
const P = getP(index1 - index0 + 1) | |
const L = 2 ** P | |
if (isOverlapping) { | |
const { value, isFromA } = op( | |
table[P][index0].value, | |
table[P][index1 - L + 1].value, | |
) | |
return { | |
value, | |
index: isFromA | |
? table[P][index0].index | |
: table[P][index1 - L + 1].index, | |
} | |
} | |
if (index1 - index0 + 1 > L) { | |
const { value: rvalue, index: rindex } = query(index0 + L, index1) | |
const { value, isFromA } = op(table[P][index0].value, rvalue) | |
return { | |
value, | |
index: isFromA ? table[P][index0].index : rindex, | |
} | |
} | |
return table[P][index0] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
RMQ (Range Minimum Query) is a problem of processing some values along the intervals. Sparse table is a genius dynamic programming approach for solving RMQ. There is a great explanation video on WilliamFiset's Youtube channel, so if you're curious about how it works, please check that out.
Sparse Table holds O(N lg N) spaces, and needs O(N lg N) operations for construction. Note that this implementation returns the value with the index. If you don't need the index, just modify slightly. Also index can be meaningless with respect to some kind of operations. For example, since summation makes completely new value, you don't have to set
isFromA
.Example
Operation's Property
To use this algorithm, operation must satisfies the associativity property. And if the operation also satisfies overlap friendly property, then the performance gets better.
Associativity
A binary operation f is associative, iff f(f(a, b), c) = f(a, f(b, c)) for all a, b, c in the domain of f.
If f is associative, then RMQ can query with in O(lg N) time complexity. Example of this kind of operations are following:
Overlap Friendly
A binary operation f is overlap friendly, iff f(f(a, b), f(b, c)) = f(f(a, b), c) for all a, b, c in the domain of f.
If f is overlap friendly, then RMQ can query with in O(1) times complexity. Example of this kind of operations are following: