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

简介: leetcode-面试题 17.20-连续中值

网络异常,图片无法展示
|


[题目地址][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,且大顶堆中元素数量大于等于小顶堆元素数量 这

样获取中位数的时候,如果两个堆元素数量相等,则中位数是两个堆顶元素的平均值

否则中位数就是大顶堆堆顶元素值


动画演示如下:


网络异常,图片无法展示
|


代码如下:


// 堆
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-连续中值


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

相关文章
|
26天前
|
开发者 索引 Python
这些年背过的面试题——LeetCode
本文是技术人面试系列LeetCode篇,一文带你详细了解,欢迎收藏!
|
2月前
|
Python
155. 最小栈 力扣 python 空间换时间 o(1) 腾讯面试题
155. 最小栈 力扣 python 空间换时间 o(1) 腾讯面试题
|
2月前
|
存储 算法 索引
1124. 表现良好的最长时间段 (python) 前缀和 分类讨论 最大长度 力扣 面试题
1124. 表现良好的最长时间段 (python) 前缀和 分类讨论 最大长度 力扣 面试题
|
2月前
|
存储 算法
经典的滑动窗口的题目 力扣 2799. 统计完全子数组的数目(面试题)
经典的滑动窗口的题目 力扣 2799. 统计完全子数组的数目(面试题)
|
3月前
|
SQL 算法 大数据
深入解析力扣176题:第二高的薪水(子查询与LIMIT详解及模拟面试问答)
深入解析力扣176题:第二高的薪水(子查询与LIMIT详解及模拟面试问答)
|
3月前
|
SQL 算法 大数据
深入解析力扣181题:超过经理收入的员工(自连接方法详解及模拟面试问答)
深入解析力扣181题:超过经理收入的员工(自连接方法详解及模拟面试问答)
|
3月前
|
存储 算法 搜索推荐
深入解析力扣179题:最大数(自定义排序法详解及模拟面试问答)
深入解析力扣179题:最大数(自定义排序法详解及模拟面试问答)
|
3月前
|
SQL 大数据 数据挖掘
深入解析力扣178题:分数排名(DENSE_RANK详解及模拟面试问答)
深入解析力扣178题:分数排名(DENSE_RANK详解及模拟面试问答)
|
3月前
|
存储 算法 数据挖掘
力扣174题动态规划:地下城游戏(含模拟面试)
力扣174题动态规划:地下城游戏(含模拟面试)
|
3月前
|
存储 算法 数据挖掘
力扣173题:二叉搜索树迭代器(含模拟面试)
力扣173题:二叉搜索树迭代器(含模拟面试)