|
1 | 1 | """
|
2 | 2 | Sparse table is a data structure that allows answering range queries on
|
3 |
| - a static array, i.e. the elements do not change throughout all the queries. |
| 3 | + a static number_listay, i.e. the elements do not change throughout all the queries. |
4 | 4 |
|
5 | 5 | The implementation below will solve the problem of Range Minimum Query:
|
6 |
| - Finding the minimum value of a subset [L..R] of a static array. |
| 6 | + Finding the minimum value of a subset [L..R] of a static number_listay. |
7 | 7 |
|
8 | 8 | Overall time complexity: O(nlogn)
|
9 | 9 | Overall space complexity: O(nlogn)
|
|
12 | 12 | """
|
13 | 13 |
|
14 | 14 |
|
15 |
| -def build_sparse_table(arr: list[int], arr_length: int) -> list[list[int]]: |
| 15 | +def build_sparse_table(number_list: list[int]) -> list[list[int]]: |
16 | 16 | """
|
17 |
| - Precompute range minimum queries with power of two length |
18 |
| - and store the precomputed values in a table. |
19 |
| -
|
20 |
| - >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7) |
| 17 | + Precompute range minimum queries with power of two length and store the precomputed |
| 18 | + values in a table. |
| 19 | + |
| 20 | + >>> build_sparse_table([8, 1, 0, 3, 4, 9, 3]) |
21 | 21 | [[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]]
|
22 |
| - >>> build_sparse_table([3, 1, 9], 3) |
| 22 | + >>> build_sparse_table([3, 1, 9]) |
23 | 23 | [[3, 1, 9], [1, 1, 0]]
|
24 |
| - >>> build_sparse_table([], 0) |
| 24 | + >>> build_sparse_table([]) |
25 | 25 | Traceback (most recent call last):
|
26 | 26 | ...
|
27 |
| - ValueError: math domain error |
| 27 | + ValueError: empty number list not allowed |
28 | 28 | """
|
29 | 29 | import math
|
30 | 30 |
|
31 |
| - if arr == []: |
32 |
| - raise ValueError("math domain error") |
| 31 | + if number_list == []: |
| 32 | + raise ValueError("empty number list not allowed") |
| 33 | + |
| 34 | + length = len(number_list) |
| 35 | + """ |
| 36 | + Initialise sparse_table |
| 37 | +
|
| 38 | + sparse_table[j][i] represents the minimum value of |
| 39 | + the subset of length (2 ** j) of number_list, starting from index i. |
| 40 | + """ |
33 | 41 |
|
34 |
| - # Initialise lookup table |
35 |
| - k = int(math.log2(arr_length)) + 1 |
36 |
| - lookup = [[0 for i in range(arr_length)] for j in range(k)] |
| 42 | + # smallest power of 2 subset length that fully covers number_list |
| 43 | + row = int(math.log2(length)) + 1 |
| 44 | + sparse_table = [[0 for i in range(length)] for j in range(row)] |
37 | 45 |
|
38 |
| - for i in range(arr_length): |
39 |
| - lookup[0][i] = arr[i] |
| 46 | + # minimum of subset of length 1 is that value itself |
| 47 | + for i, value in enumerate(number_list): |
| 48 | + sparse_table[0][i] = value |
40 | 49 |
|
41 | 50 | j = 1
|
42 | 51 |
|
43 |
| - while (1 << j) <= arr_length: |
44 |
| - # Compute the minimum value for all intervals with size (2 ** j) |
| 52 | + # compute the minimum value for all intervals with size (2 ** j) |
| 53 | + while (1 << j) <= length: |
45 | 54 | i = 0
|
46 |
| - while (i + (1 << j) - 1) < arr_length: |
47 |
| - lookup[j][i] = min(lookup[j - 1][i + (1 << (j - 1))], lookup[j - 1][i]) |
| 55 | + # while subset starting from i still have at least (2 ** j) elements |
| 56 | + while (i + (1 << j) - 1) < length: |
| 57 | + # split range [i, i + 2 ** j] and find minimum of 2 halves |
| 58 | + sparse_table[j][i] = min( |
| 59 | + sparse_table[j - 1][i + (1 << (j - 1))], |
| 60 | + sparse_table[j - 1][i] |
| 61 | + ) |
| 62 | + |
48 | 63 | i += 1
|
49 | 64 |
|
50 | 65 | j += 1
|
51 | 66 |
|
52 |
| - return lookup |
| 67 | + return sparse_table |
53 | 68 |
|
54 | 69 |
|
55 |
| -def query(lookup: list[list[int]], left_bound: int, right_bound: int) -> int: |
| 70 | +def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> int: |
56 | 71 | """
|
57 |
| - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 4) |
| 72 | + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 4) |
58 | 73 | 0
|
59 |
| - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 4, 6) |
| 74 | + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 4, 6) |
60 | 75 | 3
|
61 |
| - >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3], 7), 0, 11) |
| 76 | + >>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11) |
62 | 77 | Traceback (most recent call last):
|
63 | 78 | ...
|
64 | 79 | IndexError: list index out of range
|
65 | 80 |
|
66 |
| - >>> query(build_sparse_table([3, 1, 9], 3), 2, 2) |
| 81 | + >>> query(build_sparse_table([3, 1, 9]), 2, 2) |
67 | 82 | 9
|
68 |
| - >>> query(build_sparse_table([3, 1, 9], 3), 0, 1) |
| 83 | + >>> query(build_sparse_table([3, 1, 9]), 0, 1) |
69 | 84 | 1
|
70 | 85 |
|
71 |
| - >>> query(build_sparse_table([], 0), 0, 0) |
| 86 | + >>> query(build_sparse_table([]), 0, 0) |
72 | 87 | Traceback (most recent call last):
|
73 | 88 | ...
|
74 |
| - ValueError: math domain error |
| 89 | + ValueError: empty number list not allowed |
75 | 90 | """
|
76 | 91 | import math
|
77 | 92 |
|
78 |
| - if lookup == []: |
79 |
| - raise ValueError("math domain error") |
80 |
| - if left_bound < 0 or right_bound >= len(lookup[0]): |
| 93 | + if left_bound < 0 or right_bound >= len(sparse_table[0]): |
81 | 94 | raise IndexError("list index out of range")
|
82 | 95 |
|
| 96 | + # highest subset length of power of 2 that is within range [left_bound, right_bound] |
| 97 | + j = int(math.log2(right_bound - left_bound + 1)) |
| 98 | + |
83 | 99 | """
|
84 |
| - Find the highest power of 2 |
85 |
| - that is at least the number of elements in a given range. |
| 100 | + minimum of 2 overlapping smaller subsets: [left_bound, left_bound + 2 ** j - 1] |
| 101 | + and [right_bound - 2 ** j + 1, right_bound] |
86 | 102 | """
|
87 |
| - j = int(math.log2(right_bound - left_bound + 1)) |
88 |
| - return min(lookup[j][right_bound - (1 << j) + 1], lookup[j][left_bound]) |
| 103 | + return min( |
| 104 | + sparse_table[j][right_bound - (1 << j) + 1], |
| 105 | + sparse_table[j][left_bound] |
| 106 | + ) |
89 | 107 |
|
90 | 108 |
|
91 | 109 | if __name__ == "__main__":
|
|
0 commit comments