Back to all solutions
#3369 - Design an Array Statistics Tracker
Problem Description
Design a data structure that keeps track of the values in it and answers some queries regarding their mean, median, and mode.
Implement the StatisticsTracker class.
- StatisticsTracker(): Initialize the StatisticsTracker object with an empty array.
- void addNumber(int number): Add number to the data structure.
- void removeFirstAddedNumber(): Remove the earliest added number from the data structure.
- int getMean(): Return the floored mean of the numbers in the data structure.
- int getMedian(): Return the median of the numbers in the data structure.
- int getMode(): Return the mode of the numbers in the data structure. If there are multiple modes, return the smallest one.
Note:
- The mean of an array is the sum of all the values divided by the number of values in the array.
- The median of an array is the middle element of the array when it is sorted in non-decreasing order. If there are two choices for a median, the larger of the two values is taken.
- The mode of an array is the element that appears most often in the array.
Solution
var StatisticsTracker = function() {
this.deque = [];
this.count = new Map();
this.frequencyHeap = new PriorityQueue((a, b) => {
if (a[0] !== b[0]) return b[0] - a[0];
return a[1] - b[1];
});
this.total = 0;
this.smallHeap = new PriorityQueue((a, b) => b - a);
this.largeHeap = new PriorityQueue((a, b) => a - b);
this.largeRemove = new Map();
this.smallRemove = new Map();
this.balance = 0;
};
/**
* @param {number} number
* @return {void}
*/
StatisticsTracker.prototype.addNumber = function(number) {
this.deque.push(number);
this.total += number;
const currentCount = this.count.get(number) || 0;
this.count.set(number, currentCount + 1);
this.frequencyHeap.enqueue([this.count.get(number), number]);
if (this.largeHeap.isEmpty() || number >= this.largeHeap.front()) {
this.largeHeap.enqueue(number);
this.balance += 1;
} else {
this.smallHeap.enqueue(number);
this.balance -= 1;
}
this.keepBalance();
};
/**
* @return {void}
*/
StatisticsTracker.prototype.removeFirstAddedNumber = function() {
const value = this.deque.shift();
const currentCount = this.count.get(value);
this.count.set(value, currentCount - 1);
if (this.count.get(value) > 0) {
this.frequencyHeap.enqueue([this.count.get(value), value]);
}
this.total -= value;
if (!this.largeHeap.isEmpty() && value >= this.largeHeap.front()) {
this.largeRemove.set(value, (this.largeRemove.get(value) || 0) + 1);
this.balance -= 1;
} else {
this.smallRemove.set(value, (this.smallRemove.get(value) || 0) + 1);
this.balance += 1;
}
this.keepBalance();
};
/**
* @return {void}
*/
StatisticsTracker.prototype.keepBalance = function() {
if (this.balance > 1) {
this.smallHeap.enqueue(this.largeHeap.dequeue());
this.balance -= 2;
}
if (this.balance < 0) {
this.largeHeap.enqueue(this.smallHeap.dequeue());
this.balance += 2;
}
while (!this.smallHeap.isEmpty() && (this.smallRemove.get(this.smallHeap.front()) || 0) > 0) {
const removed = this.smallHeap.dequeue();
this.smallRemove.set(removed, this.smallRemove.get(removed) - 1);
}
while (!this.largeHeap.isEmpty() && (this.largeRemove.get(this.largeHeap.front()) || 0) > 0) {
const removed = this.largeHeap.dequeue();
this.largeRemove.set(removed, this.largeRemove.get(removed) - 1);
}
};
/**
* @return {number}
*/
StatisticsTracker.prototype.getMean = function() {
return Math.floor(this.total / this.deque.length);
};
/**
* @return {number}
*/
StatisticsTracker.prototype.getMedian = function() {
return this.largeHeap.front();
};
/**
* @return {number}
*/
StatisticsTracker.prototype.getMode = function() {
while (!this.frequencyHeap.isEmpty()) {
const [frequency, value] = this.frequencyHeap.front();
if (this.count.get(value) === frequency) {
return value;
} else {
this.frequencyHeap.dequeue();
}
}
};