
题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。题目地址
基本思路
题目的要求是动态插入后动态取出,如果插入后每次都对整个数据流进行排序的话,那么效率是非常低的。但是每次插入新的数据,肯定是要排序的,怎么才能提高效率,减小排序带来的性能损耗呢。想想中位数的特点,中间的数字,我们可否分成两个部分排序,保证一半所有数值小于另一半,这样中位数就在两者之间。而且这样插入的数字只需要让一半的数据重新排序即可。
解题方法
java中提供默认排序的集合类是非常多的,我们可以使用PriorityQueue大顶堆和小顶堆解这道题。
小顶堆定义:使用数组存储的树,根节点为最小的值,大顶堆相反,根结点是最大的值
我们使用大顶堆存储小的数,小顶堆存储较大的数。中位数就在两个堆堆顶之一或者是平均数。
需要考虑的问题
- 如何确保大顶堆里的值都小于小顶堆里的呢? 我们只要从大顶堆取出最大的值放入小顶堆即可,这样就可以保证。
- 怎么确保两个堆里数字个数均等,相等或只差一呢?
我们只需要交替的往两个堆插入数字即可,插入小顶堆时,拿出最小的插入大顶堆。插入大顶堆时拿出最大的插入小顶堆。
这样如果是偶数个数中位数就是大顶堆和小顶堆的平均值,奇数个数中位数就是不是首次插入的堆的根结点。
我们举个栗子🌰
输入的数据流是[5,2,3,4,1] max代编大顶堆,min代表小顶堆
- 插入5,我们插入min,把min最小的放入max。min[] max[5],奇数个中位数max的堆顶,也就是5
- 插入2,我们插入max, 把max最大的放入min,min[5] max[2],偶数个堆顶的平均数
- 插入3,我们插入min,把min最小的放入max,min[5],max[2,3]奇数个中位数max的堆顶,也就是3
。。。。。
代码
public class Solution {
//小顶堆
PriorityQueue<Integer> a = new PriorityQueue<>();
//大顶堆
PriorityQueue<Integer> b = new PriorityQueue<Integer>(100, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
int index = 0;
public void Insert(Integer num) {
if(index%2 != 0){
b.offer(num);
a.offer(b.poll());
}else{
a.offer(num);
b.offer(a.poll());
}
index++;
}
public Double GetMedian() {
if(index % 2 == 0){
return (a.peek() + b.peek())/2d;
}else{
return (double)b.peek();
}
}
}