[优先队列:compare] 剑指 Offer 41. 数据流中的中位数

170 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

每日刷题 2022.08.25

题目

  • 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

  • 例如,

    • [2,3,4] 的中位数是 3
    • [2,3] 的中位数是 (2 + 3) / 2 = 2.5
  • 设计一个支持以下两种操作的数据结构:

    • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
    • double findMedian() - 返回目前所有元素的中位数

示例

  • 示例1
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
  • 示例2
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制

  • 最多会对 addNum、findMedian 进行 50000 次调用。

解题思路

  • 最开始的思路是直接暴力,将数据流中的所有数都存储在一个数组中。求解中位数的时候,需要先判断数组长度是:奇数odd长度还是偶数even长度。偶数就取总长度的一半的下标和前一个下标之和/2;奇数就取总长度/2作为中位数的下标,直接根据下标就可以获取到中位数。

优先队列

  • 优先队列是由堆实现的,最大优先队列、最小优先队列。力扣在 js 提供了最大最小优先队列,直接用就可以 详细文档点这里
  • 需要注意的是,使用compare方法,可以将优先队列中存储的对象object转换成数值的形式
  • 首先创建一个最小优先队列minPrior和一个最大优先队列maxPriormaxPrior用来存储前半部分,minPrior用于存储后半部分
  • maxPrior长度大于等于minPrior长度的时候,就将新增的数值num对应的多余的元素,放入到minPrior
  • maxPrior长度小于minPrior长度的时候,就将新增的数值nums对应的多余的元素,放入到maxPrior
  • 注意:如何找到num对应的元素呢?为什么不能直接将num插入到队列中呢?(如下图所示)

211d3d6dbe2f2a0bffccfa818c4244d.jpg

  • 中位数计算:
    • 奇数:取minPrior队列头部的数值返回
    • 偶数:取minPriormaxPrior两个队列头部的两数之和/2

AC代码

var MedianFinder = function() {
  // 一个最大优先队列,一个最小的优先队列
  this.maxPrior = new MaxPriorityQueue({
    compare: (e1, e2) => e2 - e1
  });
  this.minPrior = new MinPriorityQueue({
    compare: (e1, e2) => e1 - e2
  });
  this.sum = 0;
  // 要规定一个取值位置?是从最大队列还是最小队列的顶部取值呢?
  // 规定最小队列的顶部
  // 奇数:最小队列  偶数:(最小+最大)/ 2
  // 判断两个队列的长度,让最小始终大于最等于大
};

/** 
 * @param {number} num
 * @return {void}
 */
MedianFinder.prototype.addNum = function(num) {
  this.sum++;
  // 添加元素
  let maxLen = this.maxPrior.size(), minLen = this.minPrior.size();
  if(maxLen >= minLen) {
    // 前面大于后面的,就要追加到后面
    this.maxPrior.enqueue(num);
    let cur = this.maxPrior.dequeue();
    this.minPrior.enqueue(cur);
  }else{
    // 前面的小于后面的,追加到前面
    this.minPrior.enqueue(num);
    let cur = this.minPrior.dequeue();
    this.maxPrior.enqueue(cur);
  }
};

/**
 * @return {number}
 */
MedianFinder.prototype.findMedian = function() {
  // 取中位数
  // 奇数
  if(this.sum & 1) {
    return this.minPrior.front();
  } else {
    return ((this.minPrior.front() + this.maxPrior.front()) / 2).toFixed(5);
  }
  // 偶数
};