[路飞]_前端算法第六十九弹-连续中值

105 阅读2分钟

随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[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()
};