[路飞]_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-连续中值


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

相关文章
|
8天前
|
Java
【LeetCode力扣】面试题 17.14. 最小K个数(top-k问题)
【LeetCode力扣】面试题 17.14. 最小K个数(top-k问题)
15 1
|
8天前
|
算法
【刷题】 leetcode 面试题 08.05.递归乘法
递归算法是一种在计算机科学和数学中广泛应用的解决问题的方法,其基本思想是利用问题的自我相似性,即将一个大问题分解为一个或多个相同或相似的小问题来解决。递归算法的核心在于函数(或过程)能够直接或间接地调用自身来求解问题的不同部分,直到达到基本情况(也称为基础案例或终止条件),这时可以直接得出答案而不必再进行递归调用。
28 4
【刷题】 leetcode 面试题 08.05.递归乘法
|
8天前
|
存储 算法 安全
【刷题】 leetcode 面试题 01.06 字符串压缩
来看效果: 非常好!!!过啦!!!
30 5
【刷题】 leetcode 面试题 01.06 字符串压缩
|
8天前
|
C++ 索引
【力扣经典面试题】14. 最长公共前缀
【力扣经典面试题】14. 最长公共前缀
|
8天前
|
C++
【力扣经典面试题】58. 最后一个单词的长度
【力扣经典面试题】58. 最后一个单词的长度
|
8天前
|
算法 C++
【刷题】Leetcode 1609.奇偶树
这道题是我目前做过最难的题,虽然没有一遍做出来,但是参考大佬的代码,慢慢啃的感觉的真的很好。刷题继续!!!!!!
12 0
|
8天前
|
算法 索引
【刷题】滑动窗口精通 — Leetcode 30. 串联所有单词的子串 | Leetcode 76. 最小覆盖子串
经过这两道题目的书写,相信大家一定深刻认识到了滑动窗口的使用方法!!! 下面请大家继续刷题吧!!!
16 0
|
8天前
|
存储 算法 测试技术
|
8天前
|
算法 C语言 C++