在问 · 算法题解

295. 数据流的中位数

困难栈与堆对顶双堆力扣原题 ↗

题目描述

把一组整数排好序后,位于正中间的数就是中位数。数的个数为偶数时,取中间两个数的平均值。例如 [2,3,4] 的中位数是 3,[2,3] 的中位数是 (2 + 3) / 2 = 2.5。

设计一个 MedianFinder 类,接收一串不断到来的整数,并能随时给出中位数。

MedianFinder():初始化对象。void addNum(int num):加入一个整数 num。double findMedian():返回目前已加入的所有整数的中位数,与真实值误差在 10^-5 以内即可。

输入由方法名数组和参数数组组成,输出按顺序给出每次调用的返回值,无返回值的调用记为 null。

示例 1
输入:操作 = ["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
参数 = [[],[1],[2],[],[3],[]]
输出:[null,null,null,1.5,null,2.0]

解释:加入 1、2 后中位数是 (1 + 2) / 2 = 1.5;再加入 3 后,[1,2,3] 的中位数是 2.0。

数据范围

  • -10^5 <= num <= 10^5
  • 每次调用 findMedian 时,结构里至少已有一个数。
  • addNum 和 findMedian 合计最多调用 5 * 10^4 次。

解题思路:对顶双堆

用大顶堆保存较小的一半,用小顶堆保存较大的一半;插入后平衡两个堆。

为什么正确

大顶堆的所有元素不大于小顶堆的所有元素,且两堆大小相差不超过 1。

复杂度分析

addNum O(log n),findMedian O(1),空间 O(n)。

易错点

Python heapq 只有小顶堆,左堆要存负数;取中位数时记得还原符号。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

import heapq

class MedianFinder:
    def __init__(self):
        self.left = []
        self.right = []
    def addNum(self, num: int) -> None:
        heapq.heappush(self.left, -num)
        heapq.heappush(self.right, -heapq.heappop(self.left))
        if len(self.right) > len(self.left):
            heapq.heappush(self.left, -heapq.heappop(self.right))
    def findMedian(self) -> float:
        if len(self.left) > len(self.right):
            return float(-self.left[0])
        return (-self.left[0] + self.right[0]) / 2

载入编辑器,自己跑一遍 →

其他解法

有序数组保存全部顺序,插入代价 O(n);双堆只维护“较小一半的最大值”和“较大一半的最小值”,不关心内部顺序,插入降到 O(log n)。

解法 2:有序数组 + 二分插入 时间 addNum O(n),findMedian O(1),空间 O(n)

始终维护一个有序数组。插入时先二分找到位置再插入,取中位数时直接按下标取中间一个或两个元素。

实现简单,还能顺便支持查询任意分位数。插入要搬移元素,最坏 O(n),数据量大时不如双堆,不过内存搬移很快,实际表现尚可。

from bisect import insort

class MedianFinder:
    def __init__(self):
        self.a = []

    def addNum(self, num: int) -> None:
        insort(self.a, num)

    def findMedian(self) -> float:
        a, n = self.a, len(self.a)
        if n % 2:
            return float(a[n // 2])
        return (a[n // 2 - 1] + a[n // 2]) / 2

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →