1
+ // 3636. Threshold Majority Queries
2
+ // You are given an integer array nums of length n and an array queries, where queries[i] = [li, ri, thresholdi].
3
+ // Return an array of integers ans where ans[i] is equal to the element in the subarray nums[li...ri] that appears at least thresholdi times, selecting the element with the highest frequency (choosing the smallest in case of a tie), or -1 if no such element exists.
4
+
5
+
6
+ // Solution: Mo's Algorithm
7
+
8
+ // Split nums into sqrt(n) buckets, and sort queries by l/blockSize, where blockSize = sqrt(n).
9
+ // Within each block (l/blockSize), sort queries by r in asc order.
10
+
11
+ // Process the queries in sorted order, using a sliding window keeping track of:
12
+ // Hashmap storing the current counts of each number within the window
13
+ // AVL tree storing the counts of the counts of each number within the window
14
+
15
+ // Keep track of the left and right indices in the current window.
16
+ // As we process each query, move the indices up and down according to the query and update the AVL trees.
17
+ // The right index will increment linearly within a block.
18
+ // The left index will at worst case change O(sqrt(n)) per query since that is the size of each block.
19
+
20
+ // Note: This solution is TLE - passes 556/559 test cases
21
+ // Time Complexity: O((n + q) sqrt(n) * log(n))
22
+ // Space Complexity: O(n + q)
23
+ function subarrayMajority ( nums , queries ) {
24
+ const n = nums . length , blockSize = Math . ceil ( Math . sqrt ( n ) ) ;
25
+ queries = queries . map ( ( query , i ) => [ ...query , i ] ) . sort ( ( a , b ) => {
26
+ const blockA = Math . floor ( a [ 0 ] / blockSize ) ;
27
+ const blockB = Math . floor ( b [ 0 ] / blockSize ) ;
28
+ if ( blockA !== blockB ) return blockA - blockB ;
29
+ return a [ 1 ] - b [ 1 ] ;
30
+ } ) ;
31
+ const count = { } ;
32
+ const freqCount = new AVLTree ( ( a , b ) => { // [num, freq]
33
+ if ( a [ 1 ] !== b [ 1 ] ) return b [ 1 ] - a [ 1 ] ;
34
+ return a [ 0 ] - b [ 0 ] ;
35
+ } ) ;
36
+ const res = Array ( queries . length ) ;
37
+ let currL = 0 , currR = - 1 ;
38
+ for ( let [ l , r , threshold , index ] of queries ) {
39
+ // order matters, moving l must come after moving r
40
+ while ( currR < r ) {
41
+ currR ++ ;
42
+ add ( currR ) ;
43
+ }
44
+ while ( currR > r ) {
45
+ remove ( currR ) ;
46
+ currR -- ;
47
+ }
48
+ while ( currL < l ) {
49
+ remove ( currL ) ;
50
+ currL ++ ;
51
+ }
52
+ while ( currL > l ) {
53
+ currL -- ;
54
+ add ( currL ) ;
55
+ }
56
+ res [ index ] = getAnswer ( threshold ) ;
57
+ }
58
+ return res ;
59
+
60
+ function add ( i ) {
61
+ const newCount = ( count [ nums [ i ] ] || 0 ) + 1 ;
62
+ count [ nums [ i ] ] = newCount ;
63
+ freqCount . insert ( [ nums [ i ] , newCount ] ) ;
64
+ if ( newCount > 1 ) {
65
+ freqCount . remove ( [ nums [ i ] , newCount - 1 ] ) ;
66
+ }
67
+ } ;
68
+
69
+ function remove ( i ) {
70
+ const newCount = count [ nums [ i ] ] - 1 ;
71
+ count [ nums [ i ] ] -- ;
72
+ freqCount . remove ( [ nums [ i ] , newCount + 1 ] ) ;
73
+ if ( newCount > 0 ) {
74
+ freqCount . insert ( [ nums [ i ] , newCount ] ) ;
75
+ }
76
+ } ;
77
+
78
+ function getAnswer ( threshold ) {
79
+ const max = freqCount . getKthSmallest ( 1 ) ;
80
+ if ( ! max || max [ 1 ] < threshold ) {
81
+ return - 1 ;
82
+ }
83
+ return max [ 0 ] ;
84
+ } ;
85
+ } ;
86
+
87
+ class AVLTreeNode {
88
+ constructor ( val ) {
89
+ this . val = val ;
90
+ this . left = null ;
91
+ this . right = null ;
92
+ this . height = 1 ;
93
+ this . size = 1 ; // number of nodes in tree rooted at this node
94
+ }
95
+ }
96
+ class AVLTree {
97
+ constructor ( comparator = ( a , b ) => a - b ) {
98
+ this . root = null ;
99
+ this . comparator = comparator ;
100
+ }
101
+
102
+ find ( val , node = this . root ) {
103
+ if ( ! node ) return null ;
104
+
105
+ const comparedResult = this . comparator ( val , node . val ) ;
106
+ if ( comparedResult === 0 ) return node ;
107
+ if ( comparedResult < 0 ) {
108
+ return this . find ( val , node . left ) ;
109
+ } else {
110
+ return this . find ( val , node . right ) ;
111
+ }
112
+ }
113
+
114
+ has ( val ) {
115
+ const node = this . find ( val ) ;
116
+ return ! ! node ;
117
+ }
118
+
119
+ insert ( val ) {
120
+ return ( this . root = this . _insert ( val , this . root ) ) ;
121
+ }
122
+ _insert ( val , node ) {
123
+ if ( ! node ) return new AVLTreeNode ( val ) ;
124
+
125
+ const comparedResult = this . comparator ( val , node . val ) ;
126
+ if ( comparedResult < 0 ) {
127
+ node . left = this . _insert ( val , node . left ) ;
128
+ } else {
129
+ node . right = this . _insert ( val , node . right ) ;
130
+ }
131
+ node . height =
132
+ 1 + Math . max ( this . _getHeight ( node . left ) , this . _getHeight ( node . right ) ) ;
133
+ node . size = 1 + this . getSize ( node . left ) + this . getSize ( node . right ) ;
134
+
135
+ return this . _rebalance ( node ) ;
136
+ }
137
+
138
+ remove ( val , node = this . root ) {
139
+ // To ensure we don't delete all occurances of nodes with the given value, pass in the reference of one occurance.
140
+ const nodeToRemove = this . find ( val , node ) ;
141
+ if ( ! nodeToRemove ) {
142
+ return this . root ;
143
+ }
144
+
145
+ return ( this . root = this . _remove ( val , nodeToRemove , node ) ) ;
146
+ }
147
+ _remove ( val , nodeToRemove , node ) {
148
+ if ( ! node ) return null ;
149
+
150
+ const comparedResult = this . comparator ( val , node . val ) ;
151
+ if ( comparedResult < 0 ) {
152
+ node . left = this . _remove ( val , nodeToRemove , node . left ) ;
153
+ } else if ( comparedResult > 0 ) {
154
+ node . right = this . _remove ( val , nodeToRemove , node . right ) ;
155
+ } else if ( comparedResult === 0 && node === nodeToRemove ) {
156
+ if ( ! node . left && ! node . right ) return null ;
157
+ if ( ! node . right ) return node . left ;
158
+ if ( ! node . left ) return node . right ;
159
+
160
+ // has both left and right children
161
+ // inorder traversal on the right child to get the leftmost node
162
+ // replace the node value with the leftmost node value and remove the leftmost node from the right subtree
163
+ const leftmostNode = this . _getLeftmost ( node . right ) ;
164
+ node . val = leftmostNode . val ;
165
+
166
+ node . right = this . _remove (
167
+ leftmostNode . val ,
168
+ this . find ( leftmostNode . val , node . right ) ,
169
+ node . right
170
+ ) ;
171
+ } else {
172
+ return node ;
173
+ }
174
+ node . height =
175
+ 1 + Math . max ( this . _getHeight ( node . left ) , this . _getHeight ( node . right ) ) ;
176
+ node . size = 1 + this . getSize ( node . left ) + this . getSize ( node . right ) ;
177
+
178
+ return this . _rebalance ( node ) ;
179
+ }
180
+
181
+ getKthLargestNode ( k ) {
182
+ let node = this . root ;
183
+ if ( ! node || node . size <= 0 || node . size < k ) return null ; // there is no kth element
184
+
185
+ while ( node ) {
186
+ let rightSize = node . right ?. size ?? 0 ;
187
+ if ( k === rightSize + 1 ) return node ;
188
+ if ( rightSize >= k ) {
189
+ node = node . right ;
190
+ } else {
191
+ k -= rightSize + 1 ;
192
+ node = node . left ;
193
+ }
194
+ }
195
+ return null ;
196
+ }
197
+
198
+ getKthLargest ( k ) {
199
+ const kthLargest = this . getKthLargestNode ( k ) ;
200
+ return kthLargest ?. val ?? null ;
201
+ }
202
+
203
+ getKthSmallestNode ( k ) {
204
+ let node = this . root ;
205
+ if ( ! node || node . size < k ) return null ; // there is no kth element
206
+
207
+ while ( node ) {
208
+ let leftSize = node . left ?. size ?? 0 ;
209
+ if ( k === leftSize + 1 ) return node ;
210
+ if ( leftSize >= k ) {
211
+ node = node . left ;
212
+ } else {
213
+ k -= leftSize + 1 ;
214
+ node = node . right ;
215
+ }
216
+ }
217
+ return null ;
218
+ }
219
+
220
+ getKthSmallest ( k ) {
221
+ const kthSmallest = this . getKthSmallestNode ( k ) ;
222
+ return kthSmallest ?. val ?? null ;
223
+ }
224
+
225
+ lowerBoundNode ( val , node = this . root ) {
226
+ if ( ! node ) return null ;
227
+
228
+ const comparedResult = this . comparator ( node . val , val ) ;
229
+ if ( comparedResult >= 0 ) {
230
+ const res = this . lowerBoundNode ( val , node . left ) ;
231
+ return res ? res : node ;
232
+ } else {
233
+ return this . lowerBoundNode ( val , node . right ) ;
234
+ }
235
+ }
236
+
237
+ lowerBound ( val ) {
238
+ const lowerBoundNode = this . lowerBoundNode ( val ) ;
239
+ return lowerBoundNode ?. val ?? null ;
240
+ }
241
+
242
+ upperBoundNode ( val , node = this . root ) {
243
+ if ( ! node ) return null ;
244
+
245
+ const comparedResult = this . comparator ( node . val , val ) ;
246
+ if ( comparedResult <= 0 ) {
247
+ const res = this . upperBoundNode ( val , node . right ) ;
248
+ return res ? res : node ;
249
+ } else {
250
+ return this . upperBoundNode ( val , node . left ) ;
251
+ }
252
+ }
253
+
254
+ upperBound ( val ) {
255
+ const upperBoundNode = this . upperBoundNode ( val ) ;
256
+ return upperBoundNode ?. val ?? null ;
257
+ }
258
+
259
+ _getLeftmost ( node ) {
260
+ while ( node . left ) {
261
+ node = node . left ;
262
+ }
263
+ return node ;
264
+ }
265
+
266
+ _getHeight ( node = this . root ) {
267
+ return node ? node . height : 0 ;
268
+ }
269
+
270
+ getSize ( node = this . root ) {
271
+ return node ? node . size : 0 ;
272
+ }
273
+
274
+ _getBalance ( node = this . root ) {
275
+ return node ? this . _getHeight ( node . left ) - this . _getHeight ( node . right ) : 0 ;
276
+ }
277
+
278
+ _leftRotation ( node ) {
279
+ let rightNode = node . right ;
280
+ let rightNodeLeftChild = rightNode . left ;
281
+ rightNode . left = node ;
282
+ node . right = rightNodeLeftChild ;
283
+
284
+ // node is now below rightNode and needs to be updated first
285
+ node . height =
286
+ 1 + Math . max ( this . _getHeight ( node . left ) , this . _getHeight ( node . right ) ) ;
287
+ rightNode . height =
288
+ 1 +
289
+ Math . max (
290
+ this . _getHeight ( rightNode . left ) ,
291
+ this . _getHeight ( rightNode . right )
292
+ ) ;
293
+
294
+ node . size = 1 + this . getSize ( node . left ) + this . getSize ( node . right ) ;
295
+ rightNode . size =
296
+ 1 + this . getSize ( rightNode . left ) + this . getSize ( rightNode . right ) ;
297
+
298
+ return rightNode ; // right node is the new root
299
+ }
300
+
301
+ _rightRotation ( node ) {
302
+ let leftNode = node . left ;
303
+ let leftNodeRightChild = leftNode . right ;
304
+ leftNode . right = node ;
305
+ node . left = leftNodeRightChild ;
306
+
307
+ // node is now below leftNode and needs to be updated first
308
+ node . height =
309
+ 1 + Math . max ( this . _getHeight ( node . left ) , this . _getHeight ( node . right ) ) ;
310
+ leftNode . height =
311
+ 1 +
312
+ Math . max ( this . _getHeight ( leftNode . left ) , this . _getHeight ( leftNode . right ) ) ;
313
+
314
+ node . size = 1 + this . getSize ( node . left ) + this . getSize ( node . right ) ;
315
+ leftNode . size =
316
+ 1 + this . getSize ( leftNode . left ) + this . getSize ( leftNode . right ) ;
317
+
318
+ return leftNode ; // left node is the new root
319
+ }
320
+
321
+ _rebalance ( node ) {
322
+ const balance = this . _getBalance ( node ) ;
323
+ if ( balance > 1 && this . _getBalance ( node . left ) >= 0 ) {
324
+ // left left
325
+ return this . _rightRotation ( node ) ;
326
+ } else if ( balance > 1 && this . _getBalance ( node . left ) < 0 ) {
327
+ // left right
328
+ node . left = this . _leftRotation ( node . left ) ;
329
+ return this . _rightRotation ( node ) ;
330
+ } else if ( balance < - 1 && this . _getBalance ( node . right ) <= 0 ) {
331
+ // right right
332
+ return this . _leftRotation ( node ) ;
333
+ } else if ( balance < - 1 && this . _getBalance ( node . right ) > 0 ) {
334
+ // right left
335
+ node . right = this . _rightRotation ( node . right ) ;
336
+ return this . _leftRotation ( node ) ;
337
+ }
338
+ return node ;
339
+ }
340
+ }
341
+
342
+ // Two test cases
343
+ console . log ( subarrayMajority ( [ 1 , 1 , 2 , 2 , 1 , 1 ] , [ [ 0 , 5 , 4 ] , [ 0 , 3 , 3 ] , [ 2 , 3 , 2 ] ] ) ) // [1,-1,2]
344
+ console . log ( subarrayMajority ( [ 3 , 2 , 3 , 2 , 3 , 2 , 3 ] , [ [ 0 , 6 , 4 ] , [ 1 , 5 , 2 ] , [ 2 , 4 , 1 ] , [ 3 , 3 , 1 ] ] ) ) // [3,2,3,2]
0 commit comments