Below is the implementation of the continuous median tracker:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239/** * 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); } }}
Let's break down the implementation:
Implement the continuous median tracker solution in different programming languages.
Below is the implementation of the continuous median tracker in different programming languages. Select a language tab to view the corresponding code.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239/** * 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); } }}
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.
Maintain a sorted array and insert each new number at the correct position to keep the array sorted.
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.
Use a max heap for the smaller half of the numbers and a min heap for the larger half to efficiently maintain the median.
Consider edge cases such as empty heaps, heaps with only one element, and balancing the heaps when their sizes differ by more than 1.
For numbers in a limited range, consider using counting sort or a combination of counting sort and the two heaps approach.
Verify the solution with the provided examples and additional test cases.
Modify the code to implement an alternative approach and test it with the same examples.
Implement a function that solves the continuous median tracker problem using a different approach than shown above.
The problem states that there will be at least one element before calling findMedian(), so we don't need to handle this case.
When there's only one element, it is the median.
When there's an even number of elements, the median is the average of the two middle elements.
When there's an odd number of elements, the median is the middle element.
The data structure should handle duplicate elements correctly.
Below is the implementation of the continuous median tracker:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239/** * 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); } }}
Let's break down the implementation:
Implement the continuous median tracker solution in different programming languages.
Below is the implementation of the continuous median tracker in different programming languages. Select a language tab to view the corresponding code.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239/** * 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); } }}
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.
Maintain a sorted array and insert each new number at the correct position to keep the array sorted.
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.
Use a max heap for the smaller half of the numbers and a min heap for the larger half to efficiently maintain the median.
Consider edge cases such as empty heaps, heaps with only one element, and balancing the heaps when their sizes differ by more than 1.
For numbers in a limited range, consider using counting sort or a combination of counting sort and the two heaps approach.
Verify the solution with the provided examples and additional test cases.
Modify the code to implement an alternative approach and test it with the same examples.
Implement a function that solves the continuous median tracker problem using a different approach than shown above.
The problem states that there will be at least one element before calling findMedian(), so we don't need to handle this case.
When there's only one element, it is the median.
When there's an even number of elements, the median is the average of the two middle elements.
When there's an odd number of elements, the median is the middle element.
The data structure should handle duplicate elements correctly.