剑指 offer 堆算法题:数据流中的中位数

题目描述:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。例如, [2,3,4] 的中位数是 3, [2,3] 的中位数是 (2 + 3) / 2 = 2.5

分析:

        冒泡/二分插入有序法,首先对于每个插入的元素,在插入时使其有序,取出时只需要取中间的数或中间两个数和的一半即可。

        大小根堆划分法,使用大根堆和小根堆分别保存较小和较大的一半,且在插入时保证,且小根堆比大根堆中元素个数多1或相等,如此一来,在取出时若两个堆元素个数相等,则中位数是两个堆顶元素的一半,否则是中位数是小根堆的堆顶。

求解:

class MedianFinder {
  data: number[];
  // 或者使用一个大根堆和一个小根堆分别保存一半的元素
  minPart: Heap; // 较小的一半使用大根堆保存
  maxPart: Heap; // 较大的一半使用小根堆保存
  constructor() {
    this.data = [];
    this.minPart = new Heap('max');
    this.maxPart = new Heap('min');
  }
  /**
   * 从数据流中添加一个整数到数据结构中: 大小根堆法
   * @param num
   */
  addNumHeap(num: number): void {
    if (this.minPart.isEmpty() || num <= this.minPart.top()) {
      // 小于较小的一半
      this.minPart.insert(num);
      if (this.maxPart.getSize() + 1 < this.minPart.getSize()) {
        // 较小的一半中元素个数比较大的一半中元素少2个
        this.maxPart.insert(this.minPart.delete());
      }
    } else {
      this.maxPart.insert(num);
      if (this.maxPart.getSize() > this.minPart.getSize()) {
        // 较小的一半中元素个数比较大的一半中元素多1个
        this.minPart.insert(this.maxPart.delete());
      }
    }
  }
  /**
   * 从数据流中添加一个整数到数据结构中: 二分法
   * @param num
   */
  addNumBinary(num: number): void {
    const size = this.data.length;
    if (size === 0) {
      // 数据结构为空
      this.data.push(num);
      return;
    }
    let left = 0;
    let right = size - 1;
    while (left <= right) {
      const mid = Math.floor((left + right) / 2); // (left + right) >> 1;
      const midNum = this.data[mid];
      if (midNum === num) {
        // 在该位置插入
        this.data.splice(mid, 0, num);
        return;
      }
      if (midNum > num) {
        // 插入位置在 [left, mid - 1];
        right = mid - 1;
      } else {
        // 插入位置在 [mid + 1, right];
        left = mid + 1;
      }
    }
    this.data.splice(left, 0, num);
  }
  /**
   * 从数据流中添加一个整数到数据结构中: 冒泡法
   * @param num
   * @returns
   */
  addNumBubble(num: number): void {
    this.data.push(num);
    const size = this.data.length;
    for (let i = size - 2; i >= 0 && this.data[i] > this.data[i + 1]; i--) {
      // 交换
      [this.data[i], this.data[i + 1]] = [this.data[i + 1], this.data[i]];
    }
  }
  /**
   * 返回目前所有元素的中位数: 二分法。
   */
  findMedianBinary(): number {
    const size = this.data.length;
    const mid = Math.floor(size / 2);
    if (size & 1) {
      // 奇数
      return (this.data[mid - 1] + this.data[mid]) / 2;
    }
    return this.data[mid];
  }
  /**
   * 返回目前所有元素的中位数。大小根堆法
   */
  findMedianHeap(): number {
    if (this.minPart.getSize() > this.maxPart.getSize()) {
      return this.minPart.top();
    }
    return (this.minPart.top() + this.maxPart.top()) / 2.0;
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值