Skip to content

Commit f0acaea

Browse files
committed
Add solution
1 parent 4fd06b6 commit f0acaea

File tree

1 file changed

+344
-0
lines changed

1 file changed

+344
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
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

Comments
 (0)