这道题以前就刷过,感觉一直稀里糊涂的,导致再做一次的话,即时知道要用两个堆,以及一些思路,细节还是处理不好。
方法细节不一定是最好的,但是下面这个思路是我自己可以理解且不会忘记的,记录一下。
————————————
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
————————————
大体思路很明确,因为中位数是排序之后,位于中间的数值,所以以他为界分为2个数组的话,在他之前的数都是比他小的,且他是之前数组的最大值;同理,在他之后的数都是比他大的,且他是之后数组的最小值,很自然想到前面一个大根堆,后面一个小根堆。
我们需要做的是,
(1)维持这两个堆的容量的平衡。
如果当前数据流是偶数个数,那么这两个堆的容量相等,中位数这两个堆的堆顶的平均值;如果是奇数个数,那么必然有一个堆要多一个数,多出来的这个数就是中位数。
(2)维持小根堆里的数大小都比大根堆里的数大。
这里可以随意指定一个堆的容量多1,假设我选取大根堆的容量多1个,即大根堆容量-小根堆容量<=1.
那么当addNum()时,我需要做的事情是:
(1)先什么都不管,添加到大根堆中。
(2)如果大根堆的size - 小根堆的size>1了,那么需要将大根堆的堆顶放到小根堆中,调整两个堆的大小。
(此举是保证容量的平衡)
(3)如果添加的这个Num比小根堆的堆顶要大的话,那么将他与小根堆的堆顶交换。
(因为Num已在第一步添加到大根堆中了,又Num比小根堆堆顶要大,又小根堆所有数都比大根堆中的数要大,所以这个Num肯定在大根堆的堆顶,所以此举其实就是交换两个堆的堆顶,目的是为了保证小根堆里的数都比大根堆中的数大。)
需要注意的就是第二步只会在大根堆的size已经比小根堆的size多1的时候做,所以这步做完,两个堆的大小肯定相等,而且如果Num是比小根堆的堆顶大的话,他肯定会在这步移动到小根堆中,所以做完第二步可以不用做第三步了,直接return。第三步是在大根堆的size与小根堆的size一样时候做的,以免出现大根堆中有比小根堆大的数。
class MedianFinder {
PriorityQueue<Integer> minHeap;
PriorityQueue<Integer> maxHeap;
public MedianFinder() {
minHeap = new PriorityQueue();
maxHeap = new PriorityQueue<>((x,y)->(y-x));
}
public void addNum(int num) {
maxHeap.add(num);
if(maxHeap.size()-minHeap.size()>1) {
minHeap.add(maxHeap.poll());
return;
}
if(minHeap.size()!=0 && num > minHeap.peek()) {
int temp = minHeap.poll();
minHeap.add(maxHeap.poll());
maxHeap.add(temp);
}
}
public double findMedian() {
if(maxHeap.size()==0) throw new RuntimeException("无数字!");
else if(minHeap.size() == maxHeap.size()) return (minHeap.peek()+maxHeap.peek())/2.0;
else return maxHeap.peek();
}
}