[路飞]_leetcode-面试题 17.20-连续中值

231 阅读3分钟

[题目地址] [B站地址]

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

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

例如,

[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

sort 排序

本题最简单直接的一种方式就是维护一个数组,存储所有的数字
当需要获取中位数的时候,将数组进行排序
然后根据数组中元素数量的奇偶获取中位数即可

代码如下:

var MedianFinder = function() {
  this.arr = [];
};

/**
 * @param {number} num
 * @return {void}
 */
MedianFinder.prototype.addNum = function(num) {
  this.arr.push(num);
};

/**
 * @return {number}
 */
MedianFinder.prototype.findMedian = function() {
  // 对数组排序
  this.arr.sort((a,b) => a-b)
  // 根据数组长度奇偶返回对应中位数
  const len = this.arr.length;
  if(len%2) return this.arr[len>>1]
  return (this.arr[(len>>1)-1]+this.arr[len>>1])/2
};

这种方法提交可以通过,但是因为每次都要对数组进行排序,所以效率很低

二分插入

为了避免每次对数组进行排序,我们可以将新的整数直接插入到数组的合适位置
这样就能保证数组一直有序,从而避免每次获取中位数的时候再进行排序

代码如下:

// 二分插入
var MedianFinder = function() {
  this.arr = [];
};

/**
 * @param {number} num
 * @return {void}
 */
MedianFinder.prototype.addNum = function(num) {
  // 每次将num插入到有序数组合适位置,维持数组有序
  if(this.arr.length==0||num<=this.arr[0]) this.arr.unshift(num)
  else if(num>=this.arr[this.arr.length-1]) this.arr.push(num)
  else{
    let l = 0,r = this.arr.length-1;
    while(l<r){
      const mid = (l+r)>>1;
      if(this.arr[mid]<num) l = mid+1
      else r = mid
    }
    this.arr.splice(l,0,num)
  }
};

/**
 * @return {number}
 */
MedianFinder.prototype.findMedian = function() {
  // 根据数组长度奇偶返回对应中位数
  const len = this.arr.length;
  if(len%2) return this.arr[len>>1]
  return (this.arr[(len>>1)-1]+this.arr[len>>1])/2
};

本题获取中位数,其实就是要维护一个前半部分的最大值和后半部分的最小值
维护最值,我们可以通过堆来实现
所以本题还可以通过一个大顶堆,维护前半部分较小值
通过一个小顶堆维护后半部分较大值
维护两个堆中元素数量的差值不大于 1,且大顶堆中元素数量大于等于小顶堆元素数量 这样获取中位数的时候,如果两个堆元素数量相等,则中位数是两个堆顶元素的平均值
否则中位数就是大顶堆堆顶元素值

动画演示如下:

leetcode-面试题 17.20-连续中值.gif

代码如下:

// 堆
class Heap {
  constructor(compare){
    this.arr = [];
    this.size = 0;
    this.compare = compare
  }
  // 插入操作
  push(val){
    this.arr.push(val);
    this.size++;
    // 插入后自下向上平衡调整
    if(this.size>1){
      let cur = this.size-1,
      parent = (cur-1)>>1;
      while(cur>0&&this.compare(this.arr[parent],this.arr[cur])){
        [this.arr[parent],this.arr[cur]] =
        [this.arr[cur],this.arr[parent]]
        cur = parent,parent = (cur-1)>>1;
      }
    }
  }
  // 弹出操作
  pop(){
    const res = this.arr[0];
    this.arr[0] = this.arr.pop();
    this.size--;
    // 弹出后自上向下平衡调整
    let cur = 0,
    childl = 1,childr = 2;
    while(
      (childl<this.size&&this.compare(this.arr[cur],this.arr[childl])) ||
      (childr<this.size&&this.compare(this.arr[cur],this.arr[childr]))
    ){
      if(childr<this.size&&this.compare(this.arr[childl],this.arr[childr])){
        [this.arr[cur],this.arr[childr]] =
        [this.arr[childr],this.arr[cur]]
        cur = childr
      }else{
        [this.arr[cur],this.arr[childl]] =
        [this.arr[childl],this.arr[cur]]
        cur = childl
      }
      childl = cur*2+1,childr = cur*2+2;
    }
    return res;
  }
  // 获取堆顶元素
  top(){
    return this.arr[0]
  }
}
var MedianFinder = function() {
  // 创建大顶堆,小顶堆实例
  this.bigHeap = new Heap((a,b) => a<b)
  this.littleHeap = new Heap((a,b) => a>b)
};

/**
 * @param {number} num
 * @return {void}
 */
MedianFinder.prototype.addNum = function(num) {
  // 当两个堆为空或者当前值小于等于大顶堆堆顶元素的时候,插入大顶堆
  if(this.bigHeap.size===0||num<=this.bigHeap.top()){
    this.bigHeap.push(num)
    // 如果大顶堆元素数量大于小顶堆元素数量+1
    // 弹出大顶堆堆顶元素插入小顶堆
    if(this.bigHeap.size>this.littleHeap.size+1){
      this.littleHeap.push(this.bigHeap.pop())
    }
  }else{
    this.littleHeap.push(num)
    // 如果小顶堆元素数量大于大顶堆
    // 弹出小顶堆堆顶元素插入大顶堆
    if(this.littleHeap.size>this.bigHeap.size){
      this.bigHeap.push(this.littleHeap.pop())
    }
  }
};

/**
 * @return {number}
 */
MedianFinder.prototype.findMedian = function() {
  // 如果两堆元素数量相等,中位数是两堆顶元素平均值
  if(this.bigHeap.size===this.littleHeap.size){
    return (this.bigHeap.top()+this.littleHeap.top())/2
  }
  // 否则中位数是大顶堆堆顶元素
  return this.bigHeap.top();
};

至此我们就完成了 leetcode-面试题 17.20-连续中值

如有任何问题或建议,欢迎留言讨论!