题目:
得到一个数据流中的中位数。若为奇数,那么中位数就是所有数排序之后位于中间的值;若为偶数,那么中位数就是所有数值排序之后中间两个数的平均数。 [2,3,4] 返回3;[2,3]返回2.5 要求实现两个函数:
- void addNum(int num) - 从数据流中添加一个整数到数据结构
- double findMedian() - 返回目前所有元素的中位数
思路:
- 先用java集合PriorityQueue设置一个大顶堆,一个小顶堆(默认是小顶堆,也就是数字从小到大排序,小的先出队列)
- 大顶堆用来存较小的数,按从大到小的顺序排列;小顶堆存较大的数,按从小到大的顺序排列
- 这样,小顶堆里的所有数均大于等于大顶堆中的数。这样中位数就是大顶堆队首和小顶堆队首的平均值了。
- 每次进队列的时候,不是直接进,而是先进队列,然后poll出一个数,进另一个队列
- count用于记录当前两个队列里数字个数之和
- 当count为偶数时,说明插入这个数字之后一共有奇数个数字,此时的中位数就是一个数。我们先让它进大顶堆,然后将大顶堆中的最大值(大顶堆中的最大值)弹出插入小顶堆中。最后的中位数就是小顶堆的队首元素。
- 当count为奇数时,说明插入这个数字之后是偶数个数字,此时的中位数就是两个数之和。原本为奇数,其实是小顶堆里的数字个数多一个,所以这个新插入的数字需要进大顶堆。所以我们先把数字插入小顶堆中,再将小顶堆中的根节点(小顶堆中的最小值)插入到大顶堆中。
举例:
传入数据为:[5,2,3,4,1,6,7,0,8]应该输出为[5 3.5 3 3.5 3 3.5 4 3.5 4] min表示小顶堆,max表示大顶堆
- count=0;5进大顶堆,大顶堆中弹出最大值进小顶堆,结果为小顶堆的队首元素 min=[5] max=[] res=[5.00]
- count=1;2进小顶堆,小顶堆弹出最小值进大顶堆,结果为两个顶堆队首元素的平均值 min=[5] max=[2] res=[3.50]
- count=2;3进大顶堆,大顶堆中弹出最大值进小顶堆,结果为小顶堆的队首元素 min=[3,5] max=[2], res=[3.00]
- 以此类推
注意点
- 需要重写优先队列的迭代器,默认是小顶堆,大顶堆需要重写:
PriorityQueue<Integer> max = new PriorityQueue<>(15, new Comparator<Integer>(){
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
- Double类型的使用:
new Double((min.peek() + max.peek()))/2和 new Double((min.peek() + max.peek())/2)是两个不同的结果,我们需要的是第一种
代码
class MedianFinder {
PriorityQueue<Integer> min;
PriorityQueue<Integer> max;
int count = 0;
/** initialize your data structure here. */
public MedianFinder() {
min = new PriorityQueue<>();
max = new PriorityQueue<>(15, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
}
public void addNum(int num) {
if (count % 2 == 0) {
max.offer(num);
min.offer(max.poll());
} else {
min.offer(num);
max.offer(min.poll());
}
count++;
}
public double findMedian() {
if (count % 2 == 1) {
return new Double(min.peek());
} else {
return new Double((min.peek() + max.peek())) / 2;
}
}
}
使用如下:
MedianFinder obj = new MedianFinder();
obj.addNum(num);
double param_2 = obj.findMedian();