101 Logo
onenoughtone

Code Implementation

Continuous Median Tracker Implementation

Below is the implementation of the continuous median tracker:

solution.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/**
* Binary Search Insertion Approach
* Time Complexity: O(n) for addNum, O(1) for findMedian
* Space Complexity: O(n) - We need to store all elements
*/
class MedianFinderBinarySearch {
constructor() {
this.nums = [];
}
/**
* Adds a number to the data structure.
* @param {number} num - The number to add
* @return {void}
*/
addNum(num) {
// Use binary search to find the insertion position
let left = 0;
let right = this.nums.length;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (this.nums[mid] < num) {
left = mid + 1;
} else {
right = mid;
}
}
// Insert the number at the correct position
this.nums.splice(left, 0, num);
}
/**
* Returns the median of all numbers added so far.
* @return {number} - The median
*/
findMedian() {
const n = this.nums.length;
if (n % 2 === 1) {
// Odd number of elements
return this.nums[Math.floor(n / 2)];
} else {
// Even number of elements
return (this.nums[n / 2 - 1] + this.nums[n / 2]) / 2;
}
}
}
/**
* Two Heaps Approach
* Time Complexity: O(log n) for addNum, O(1) for findMedian
* Space Complexity: O(n) - We need to store all elements
*/
class MedianFinder {
constructor() {
// Max heap for the smaller half
this.smallerHalf = new MaxHeap();
// Min heap for the larger half
this.largerHalf = new MinHeap();
}
/**
* Adds a number to the data structure.
* @param {number} num - The number to add
* @return {void}
*/
addNum(num) {
// Add the number to the appropriate heap
if (this.smallerHalf.size() === 0 || num <= this.smallerHalf.peek()) {
this.smallerHalf.add(num);
} else {
this.largerHalf.add(num);
}
// Balance the heaps
if (this.smallerHalf.size() > this.largerHalf.size() + 1) {
this.largerHalf.add(this.smallerHalf.poll());
} else if (this.largerHalf.size() > this.smallerHalf.size()) {
this.smallerHalf.add(this.largerHalf.poll());
}
}
/**
* Returns the median of all numbers added so far.
* @return {number} - The median
*/
findMedian() {
if (this.smallerHalf.size() > this.largerHalf.size()) {
// Odd number of elements, smallerHalf has one more element
return this.smallerHalf.peek();
} else {
// Even number of elements, both heaps have the same size
return (this.smallerHalf.peek() + this.largerHalf.peek()) / 2;
}
}
}
/**
* Max Heap implementation
*/
class MaxHeap {
constructor() {
this.heap = [];
}
size() {
return this.heap.length;
}
isEmpty() {
return this.size() === 0;
}
peek() {
return this.heap[0];
}
add(val) {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
poll() {
if (this.isEmpty()) {
return null;
}
const max = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.siftDown(0);
}
return max;
}
siftUp(index) {
let parent = Math.floor((index - 1) / 2);
while (index > 0 && this.heap[parent] < this.heap[index]) {
[this.heap[parent], this.heap[index]] = [this.heap[index], this.heap[parent]];
index = parent;
parent = Math.floor((index - 1) / 2);
}
}
siftDown(index) {
const left = 2 * index + 1;
const right = 2 * index + 2;
let largest = index;
if (left < this.heap.length && this.heap[left] > this.heap[largest]) {
largest = left;
}
if (right < this.heap.length && this.heap[right] > this.heap[largest]) {
largest = right;
}
if (largest !== index) {
[this.heap[index], this.heap[largest]] = [this.heap[largest], this.heap[index]];
this.siftDown(largest);
}
}
}
/**
* Min Heap implementation
*/
class MinHeap {
constructor() {
this.heap = [];
}
size() {
return this.heap.length;
}
isEmpty() {
return this.size() === 0;
}
peek() {
return this.heap[0];
}
add(val) {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
poll() {
if (this.isEmpty()) {
return null;
}
const min = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.siftDown(0);
}
return min;
}
siftUp(index) {
let parent = Math.floor((index - 1) / 2);
while (index > 0 && this.heap[parent] > this.heap[index]) {
[this.heap[parent], this.heap[index]] = [this.heap[index], this.heap[parent]];
index = parent;
parent = Math.floor((index - 1) / 2);
}
}
siftDown(index) {
const left = 2 * index + 1;
const right = 2 * index + 2;
let smallest = index;
if (left < this.heap.length && this.heap[left] < this.heap[smallest]) {
smallest = left;
}
if (right < this.heap.length && this.heap[right] < this.heap[smallest]) {
smallest = right;
}
if (smallest !== index) {
[this.heap[index], this.heap[smallest]] = [this.heap[smallest], this.heap[index]];
this.siftDown(smallest);
}
}
}

Step-by-Step Explanation

Let's break down the implementation:

  1. 1. Understand the Problem: First, understand that we need to design a data structure that can efficiently add numbers from a data stream and find the median of all numbers added so far.
  2. 2. Implement the Brute Force Approach: Maintain a sorted array and insert each new number at the correct position to keep the array sorted.
  3. 3. Optimize with Binary Search: Use binary search to find the insertion position more efficiently, though the overall time complexity for insertion is still O(n) due to shifting elements.
  4. 4. Implement the Two Heaps Approach: Use a max heap for the smaller half of the numbers and a min heap for the larger half to efficiently maintain the median.
  5. 5. Handle Edge Cases: Consider edge cases such as empty heaps, heaps with only one element, and balancing the heaps when their sizes differ by more than 1.
  6. 6. Optimize for Follow-up Questions: For numbers in a limited range, consider using counting sort or a combination of counting sort and the two heaps approach.
  7. 7. Test the Solution: Verify the solution with the provided examples and additional test cases.
ProblemSolutionCode
101 Logo
onenoughtone