如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[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]
解决方案:
由题目得,数据流是将数值一个一个地输入程序,即过程中,中位数的变化是逐元素的。
其过程如下所示,用红色表示中位数节点:
从元素的逐个添加过程,我们可以发现,当我们将已有的n个数据分解为两部分, S s m a l l = { x ∣ x < = 中位数 } , S b i g = { x ∣ x > 中位数 } S_{small}=\{x|x<=中位数\},S_{big}=\{x|x>中位数\} Ssmall={x∣x<=中位数},Sbig={x∣x>中位数}
当 n 为奇数时, l e n ( S s m a l l ) = n + 1 2 , l e n ( S b i g ) = n − 1 2 ; 当 n 为偶数时, l e n ( S s m a l l ) = n 2 , l e n ( S b i g ) = n 2 ; 当n为奇数时,len(S_{small}) = \frac{n+1}{2},len(S_{big})=\frac{n-1}{2};\newline 当n为偶数时,len(S_{small}) = \frac{n}{2},len(S_{big})=\frac{n}{2}; 当n为奇数时,len(Ssmall)=2n+1,len(Sbig)=2n−1;当n为偶数时,len(Ssmall)=2n,len(Sbig)=2n;
若我们能够通过算法使得在数据插入后,仍能将已有数据管理为上述结构,则当我们想要获取中位数时,中位数为:
设 A 为 S s m a l l 集中的最大值 , B 为 S b i g 中的最小值 , 则: 当 n 为奇数时,中位数 = A 当 n 为偶数时,中位数 = A + B 2 设A为S_{small}集中的最大值,B为S_{big}中的最小值, 则:\newline 当n为奇数时,中位数=A \newline 当n为偶数时,中位数=\frac{A+B}{2} 设A为Ssmall集中的最大值,B为Sbig中的最小值,则:当n为奇数时,中位数=A当n为偶数时,中位数=2A+B
此时,我们将问题转化,如何将数据管理成上述结构;
从元素数量的角度出发,可以发现,当n为奇数时,应向 S b i g S_{big} Sbig中添加元素;n为偶数时,应向 S s m a l l S_{small} Ssmall中添加元素,并使得两个集合中的元素仍满足定义。
进一步地,问题转为如何插入使得集合中元素仍满足定义。
新进元素C有以下情况:
- C ≤ A
- A < C < B
- B≤C
当n为奇数时,对于以上三种情况,需要执行以下操作
- 将C插入 S s m a l l S_{small} Ssmall
- 将C插入 S s m a l l S_{small} Ssmall
- 将B取出,插入 S s m a l l S_{small} Ssmall, 再将C插入 S b i g S_{big} Sbig
值得注意的是,存在A≤B的关系,则当将C插入 S b i g S_{big} Sbig时,此时 S b i g S_{big} Sbig的最小值new_B的值为:
- new_B = C
- new_B = C
- new_B = B
所以上述操作可以简写为,将C插入 S b i g S_{big} Sbig,将new_B取出插入 S s m a l l S_{small} Ssmall。
当n为偶数时,对于以上三种情况,需要执行以下操作:
- 将A取出,插入 S b i g S_{big} Sbig, 将C插入 S s m a l l S_{small} Ssmall
- 将C插入 S b i g S_{big} Sbig
- 将C插入 S b i g S_{big} Sbig
与n为奇数时,同理,该操作可简写为,将C插入 S s m a l l S_{small} Ssmall,将new_A取出插入 S b i g S_{big} Sbig。
由于我们需要频繁地从 S s m a l l S_{small} Ssmall取出最大值,从 S b i g S_{big} Sbig取出最小值,所以我们可以使用大顶堆和小顶堆分别维护 S s m a l l S_{small} Ssmall和 S b i g S_{big} Sbig,****堆的实现代码可以看博客:堆的图示以及代码实现**。
实现代码如下:
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.min_heap = Heap(lambda x,y:x<=y)
self.max_heap = Heap(lambda x,y:x>=y)
def addNum(self, num: int) -> None:
if len(self.min_heap) != len(self.max_heap):
self.min_heap.insert(num)
self.max_heap.insert(self.min_heap.delete())
else:
self.max_heap.insert(num)
self.min_heap.insert(self.max_heap.delete())
def findMedian(self) -> float:
# return [self.min_heap.store,[],self.max_heap.store,[],[]]
return self.min_heap[0] if len(self.min_heap) != len(self.max_heap) else (self.min_heap[0] + self.max_heap[0])/2.0