class Heap {
data: number[] = [];
comparator: (a: number, b: number) => boolean;
constructor(comparator: (a: number, b: number) => boolean) {
this.comparator = comparator;
}
size(): number {
return this.data.length;
}
peek(): number | null {
return this.data.length === 0 ? null : this.data[0];
}
push(val: number): void {
this.data.push(val);
this.bubbleUp();
}
pop(): number | null {
if (this.data.length === 0) return null;
const top = this.data[0];
const end = this.data.pop()!;
if (this.data.length > 0) {
this.data[0] = end;
this.bubbleDown();
}
return top;
}
bubbleUp(): void {
let index = this.data.length - 1;
const element = this.data[index];
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
const parent = this.data[parentIndex];
if (this.comparator(element, parent)) {
this.data[index] = parent;
index = parentIndex;
} else {
break;
}
}
this.data[index] = element;
}
bubbleDown(): void {
let index = 0;
const length = this.data.length;
const element = this.data[0];
while (true) {
let leftChildIndex = 2 * index + 1;
let rightChildIndex = 2 * index + 2;
let swapIndex = -1;
if (leftChildIndex < length) {
if (this.comparator(this.data[leftChildIndex], element)) {
swapIndex = leftChildIndex;
}
}
if (rightChildIndex < length) {
if (
this.comparator(this.data[rightChildIndex],
swapIndex === -1 ? element : this.data[leftChildIndex])
) {
swapIndex = rightChildIndex;
}
}
if (swapIndex === -1) break;
this.data[index] = this.data[swapIndex];
index = swapIndex;
}
this.data[index] = element;
}
}
class MedianFinder {
maxHeap: Heap; // smaller half
minHeap: Heap; // larger half
constructor() {
this.maxHeap = new Heap((a, b) => a > b); // max heap
this.minHeap = new Heap((a, b) => a < b); // min heap
}
addNum(num: number): void {
if (this.maxHeap.size() === 0 || num <= this.maxHeap.peek()!) {
this.maxHeap.push(num); // add to smaller half
} else {
this.minHeap.push(num); // add to larger half
}
// balance heaps sizes
if (this.maxHeap.size() > this.minHeap.size() + 1) {
this.minHeap.push(this.maxHeap.pop()!); // move largest from smaller half to larger half
} else if (this.minHeap.size() > this.maxHeap.size()) {
this.maxHeap.push(this.minHeap.pop()!); // move smallest from larger half to smaller half
}
}
findMedian(): number | null {
if (this.maxHeap.size() === 0 && this.minHeap.size() === 0) {
return null; // no elements
}
if (this.maxHeap.size() > this.minHeap.size()) {
return this.maxHeap.peek()!; // odd count, max heap top is median
} else {
return (this.maxHeap.peek()! + this.minHeap.peek()!) / 2; // even count, average of tops
}
}
}
// Driver code
const mf = new MedianFinder();
const stream = [5, 2, 8, 3];
for (const num of stream) {
mf.addNum(num);
console.log(mf.findMedian());
}if (this.maxHeap.size() === 0 || num <= this.maxHeap.peek()!) {
decide which heap to add the new number to keep smaller half and larger half
if (this.maxHeap.size() > this.minHeap.size() + 1) {
balance heaps if smaller half has more than one extra element
else if (this.minHeap.size() > this.maxHeap.size()) {
balance heaps if larger half has more elements
if (this.maxHeap.size() > this.minHeap.size()) {
if odd count, median is top of smaller half
else { return (this.maxHeap.peek()! + this.minHeap.peek()!) / 2; }
if even count, median is average of tops of both heaps