295. 数据流的中位数
原始题目链接:https://leetcode-cn.com/problems/find-median-from-data-stream/
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[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]
解题思路:
给定一长度为 NN 的无序数组,其中位数的计算方法:首先对数组执行排序(O(NlogN)时间),然后返回中间元素即可( O(1)时间)。借助堆可以进一步优化时间复杂度,建立一个 小顶堆A和大顶堆B,各保存列表的一半元素,且规定: A保存较大的一半数据,长度为N/2,N为偶数或者(N+1)/2,N为奇数B保存较小的一版数据,长度为N/2,N为偶数或者(N+1)/2,N为奇数> 这样中位数可以根据堆顶元素计算获得,注意python中的堆是小根堆,大根堆可以根据数据取反来实现,具体见代码实现。
addNum(num) 函数:
- 当 m=n(即 N 为 偶数):需向A 添加一个元素。实现方法:将新元素 num 插入至 B ,再将 B 堆顶元素插入至 A ;
- 当 m != n(即 N 为 奇数):需向 B添加一个元素。实现方法:将新元素 num 插入至 A ,再将 A 堆顶元素插入至 B ;
findMedian() 函数:
当 m != n(即 N 为 奇数):则中位数为A 的堆顶元素。 当 m=n(即 N 为 偶数):则中位数为( A 的堆顶元素 + B
的堆顶元素 )/2。
复杂度分析
- 时间复杂度: 查找中位数即获取堆顶元素O(1),添加数字操作即堆的插入和弹出操作O(logN)
- 空间复杂度:小根堆和大根堆最多共同保存N个元素,即O(N)
代码实现:
import heapq
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
# python中的堆都是小根堆,即对顶元素一定是最小值
# 大根堆的实现,用小根堆的出堆入堆操作再将数据取反实现
# 小根堆->大根堆链接在一起:数据是从大到小的
# 大根堆用于存储较小数据部分
self.big_heap = []
# 小根堆用于存储较大数据部分
self.small_heap = []
def addNum(self, num: int) -> None:
# 如果两个堆的元素个数不同,则将数据存放到大根堆中
# 存放的规则是先将数据push进小根堆,然后pop出堆顶元素到大根堆中
if len(self.big_heap) != len(self.small_heap):
heapq.heappush(self.small_heap, num)
heapq.heappush(self.big_heap, -heapq.heappop(self.small_heap))
else:
# 两个堆元素的个数相同,则将数据存放到小根堆中
# 存放的规则是先将数据push到大根堆中,然后pop出堆顶元素到小根堆中
heapq.heappush(self.big_heap, -num)
heapq.heappush(self.small_heap, -heapq.heappop(self.big_heap))
def findMedian(self) -> float:
# 计算中位数,区分奇偶情况
return self.small_heap[0] if len(self.small_heap) != len(self.big_heap) else (self.small_heap[0] - self.big_heap[0]) / 2.0
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
参考文献:
https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/mian-shi-ti-41-shu-ju-liu-zhong-de-zhong-wei-shu-y/