0%

随时找到数据流的中位数

题目 (LeetCode #189)

有一个源源不断地生成整数的数据流, 假设你有足够的空间来保存生成的数. 设计一个名叫 MedianHolder 的结构, 使之可以随时取得之前生成的所有数的中位数.

要求

  1. 如果 MedianHolder 已经保存了生成的 N 个数, 那么任意时刻将一个新数加入到 MedianHolder 的过程, 其时间复杂度是 O(logN)
  2. 取得已经产生的 N 个数整体的中位数的过程, 时间复杂度为 O(1)

题解

创建一个大根堆和一个小根堆, 尽量保证两个堆中元素数量均为 n/2 , 在添加元素时, 新元素若大于大根堆堆顶元素则放入小根堆, 若小于小根堆堆顶则放入大根堆, 这样即可保证前 n/2 个数都在大根堆中, 后 n/2 个数都在小根堆中, 中位数即为两堆的堆顶元素. 添加元素的同时确保小根堆的长度不小于大根堆, 若最后数据总数为奇数, 中位数即为小根堆堆顶元素; 若为偶数则为两堆堆顶元素和的一半.

添加新数的时间复杂度即为堆调整的时间复杂度, 即 O(logN) , 取得中位数的时间复杂度为 O(1)

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MedianHolder {
private PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
private PriorityQueue<Integer> minHeap = new PriorityQueue<>();

public void addNum(int num) {
minHeap.add(num);
maxHeap.add(minHeap.poll());
if (minHeap.size() < maxHeap.size())
minHeap.add(maxHeap.poll());
}

public double getMedian() {
if (maxHeap.size() == minHeap.size()) {
return (maxHeap.peek() + minHeap.peek()) / 2.0;
} else {
return minHeap.peek();
}
}
}

Python

heapq 中默认定义为小根堆, 通过保存相反数实现大根堆.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from heapq import *
class MedianFinder:
def __init__(self):
self.max_heap = []
self.min_heap = []

def addNum(self, num):
heappush(self.min_heap, num)
heappush(self.max_heap, -heappop(self.min_heap))
if len(self.min_heap) < len(self.max_heap):
heappush(self.min_heap, -heappop(self.max_heap))

def findMedian(self):
max_len = len(self.max_heap)
min_len = len(self.min_heap)

if min_len == 0:
return

return self.min_heap[0] if max_len != min_len else (-self.max_heap[0] + self.min_heap[0]) / 2.0