随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
这道题我们需要对每一次加入的数值进行排序,然后找到其中位数并返回,需要强调的就是排序问题。
最开始的时候我使用数组每次排序,然后取中间值进行计算中位数,但是超出了时间限制,后来我单独使用大顶堆,但是每次都要pop()一半的数,然后再push回去。也会导致超时问题。
最后总结下来,要使用大小顶堆。
大小顶堆
我们使用大小顶堆,将小值放入大顶堆中,将大值放入小顶堆中,当全放完毕后,判断大小顶堆的数量,如果两者相同,取两者的顶部,进行计算,或取多一位的顶部返回。
实现方式
- 先将第一个数放入大顶堆中
- 后加入的数如果大于大顶堆的堆顶,放于小顶堆中,反之放于大顶堆中。
- 判断大小顶堆的容积。
- 如果小顶堆比大顶堆多,则将小顶堆堆顶放入大顶堆中
- 如果大顶堆比小顶堆的数多于一个,则将大顶堆的堆顶放入小顶堆中。
- 取值的时候判断两个堆的容积,
- 相同的话,去两个堆顶取中间值
- 不同的话,取大顶堆的堆顶
首先我们实现了一个自适应的大小顶堆的类
class Heap {
constructor(cmp = "large") {
if (cmp == "large") {
this.cmp = this.large;
} else if (cmp == "small") {
this.cmp = this.small
} else {
this.cmp = cmp
}
this.res = [];
this.cnt = 0;
}
push (val) {
let { res, cnt, cmp } = this;
res[cnt] = val;
this.cnt++;
while (cnt) {
let par = (cnt - 1) >> 1;
if (!this.cmp(res[par], res[cnt])) return;
this.swap(res, cnt, par)
cnt = par;
par = (cnt - 1) >> 1;
}
}
pop () {
if (this.size() == 0) return;
this.cnt--;
let { res, cnt, cmp } = this;
const el = res[0];
this.swap(res, 0, cnt);
let idx = 0, l = idx * 2 + 1, r = idx * 2 + 2;
while (l < cnt) {
let temp = idx;
if (cmp(res[idx], res[l])) temp = l;
if (r < cnt && cmp(res[temp], res[r])) temp = r;
else if (res[temp] == res[idx]) break;
this.swap(res, temp, idx);
idx = temp;
l = idx * 2 + 1, r = idx * 2 + 2;
}
this.cmp = cmp
return el;
}
large = (a, b) => a < b
small = (a, b) => a > b;
swap = (res, i, j) => [res[i], res[j]] = [res[j], res[i]];
top = () => this.res[0];
size = () => this.cnt;
isEmpty = () => this.cnt == 0
}
初始化的时候,创建大小顶堆
var MedianFinder = function () {
this.large = new Heap("large")
this.small = new Heap("small")
};
addNum添加元素
MedianFinder.prototype.addNum = function (num) {
let { large, small } = this
if (large.isEmpty()) {
large.push(num)
} else {
large.top() > num ? large.push(num) : small.push(num)
}
let max = large.size(), min = small.size()
if (min > max) large.push(small.pop())
else if (max - 1 > min) small.push(large.pop())
};
findMedian返回中位数
MedianFinder.prototype.findMedian = function () {
let { large, small } = this;
let max = large.size(), min = small.size()
if (max == min) return (large.top() + small.top()) / 2
else return large.top()
};