思路
要取得中位数,所以只要关注前一半数字的最小值和后一段数字的最大值,采用堆来进行维护
https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/jian-zhi-41shu-ju-liu-zhong-wei-shu-shua-w7yh/
# 奇数时向小根堆添加,但是确保向小根堆中添加元素比大根堆元素大,故先加入大根堆,将大根堆堆顶弹出给小根堆
#偶数时向左边大根堆添加,但是确保向大根堆添加元素均比小根堆元素小,故先加入小根堆,将小根堆堆顶弹出给大根堆
#注意,python中只有小根堆,故大根堆采用小根堆+取反操作实现
# 插入负数到小根堆中,越大的数字插入的负数就越小,所以这样就相当于做了大根堆,注意弹出时再取反得到正数即可
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.A = []#小根堆
self.B = []#大根堆
##注意新生先入普通班(大顶堆),此时可能会失去平衡了!
def addNum(self, num):
if len(self.A) != len(self.B):
heapq.heappush(self.A , num)
heapq.heappush(self.B , -heapq.heappop(self.A))
else:
heapq.heappush(self.B , -num)
heapq.heappush(self.A , -heapq.heappop(self.B))
def findMedian(self):
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0])/2.0
用大顶堆+小顶堆方法,可以看作大顶堆是普通班,小顶堆是实验班。数量上时刻保持 小顶-大顶<=1(两堆相等或者小顶比大顶多一个)。
新学生先入普通班(大顶堆),此时可能会失去平衡了,于是取大顶堆的第一个(班里最好的学生)加入实验班(小顶堆),判断若数量过多(不是等于或多一个),取第一个(实验班里最差的学生)到普通班(大顶堆)里。 取中位数的时候,若两堆数量相等,则各取堆顶取平均,若小顶比大顶多一,则多的那一个就是中位数。
利用堆排序,构造两个堆,一个小顶堆big用来存储数据流中比较大的数,一个大顶堆small用来存储数据流中比较小的数,另外必须保证两个堆的大小相差不能超过1,这样一来,如果数据流总数是偶数,那么结果就是两个堆的堆顶元素之和除以2,如果是奇数,哪个堆的数据元素多,就返回哪个堆的堆顶。
1.为什么这个思路是对的?
从这个角度来看,如果把数据从小到大排列,把数据分成两组,左边的数比右边的数都小,如果数据总量是奇数,那么一定返回的是中间的数,这个中间的数对于较小的数据(大顶堆)来说是最大的数(堆顶),对于较大的数据来说(小顶堆)是最小的数(堆顶),所以哪个堆的元素多,就返回哪个堆的堆顶。
如果数据流是偶数,那么结果就是中间的两个数的和再除以2,那么也就是说一个数是较小数据的最大值,一个数是较大数据的最小值,结果就是两个堆的堆顶元素之和除以2。
进一步地,也就是说,不需要对数进行精确排序,我只要能保证一个堆里都是较小的数,一个堆里都是较大的数,也就是大致分个组,并且保证两个堆的尺寸相差不超过1即可。
2.问题来了,如何保证把数据能够大致分组?
这样做,如果small堆(放较小的数)为空,优先把数放到这里。接着,来了一个数据,如果这个数据比small.top()小,那我就把插入small里面,如果比它大,就放到big里面,
如果small.size()-big.size()==2,就把small的堆顶元素弹出放到big里面,因为small的堆顶元素是较小数据里面最大的那个,放到big里面,可保证big里面的数都是偏大的。
如果big.size()-small.size()==2,就把big的堆顶元素弹出放到small里面,因为big的堆顶元素是较大数据里面最小的那个,放到small里面,可保证small里面的数都是偏小的。