描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
示例
input:
[5,2,3,4,1,6,7,0,8]
output:
5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00
note:
数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...
思路
1、暴力求解:直接添加+排序取中位数
该方法比较容易想到,直接使用一个数组来记录数据流,取中位数前先排序。该方法的关键在于排序算法的选取。本题中采用ArrayList
存储元素,并使用sort
或者用Collections.sort
排序。时间复杂度为O(nlogn)
。如果采用冒泡等排序,则时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
import java.util.*;
public class Solution {
ArrayList<Integer> arr = new ArrayList<>();
public void Insert(Integer num) {
arr.add(num);
}
public Double GetMedian() {
//Collections.sort(arr); //21ms
arr.sort((o1, o2)->o1.compareTo(o2)); //116ms
int mid = arr.size() >> 1;
if( (arr.size()&1) == 0) return (arr.get(mid)+arr.get(mid-1))/2.0;
else return arr.get(mid)*1.0;
}
}
1、大小堆
由于需要求中位数,因此考虑将数组按照中位数mid
分为两部分:比中位数小的大顶堆max_heap
、比中位数大或相等的小顶堆min_heap
。这样可以保证:max_heap.peek() < mid <= min_heap.peek()
。在实现上,先将元素放入大顶堆排序,然后取大顶堆的堆顶元素放入小顶堆,如果小顶堆大小比大顶堆多2,则让两个顶堆大小一样。
import java.util.*;
public class Solution {
//小顶堆存储较大元素,大顶堆存储较小的元素
PriorityQueue<Integer> min_heap = new PriorityQueue<>();
PriorityQueue<Integer> max_heap = new PriorityQueue<>(Comparator.reverseOrder());
public void Insert(Integer num) {
//大顶堆筛选后,将大顶堆中最大的元素放入小顶堆
max_heap.offer(num);
min_heap.offer(max_heap.poll());
//由于第1个元素放入在min_heap中,所以奇数个元素时min_heap多1个
if(min_heap.size() - max_heap.size() > 1){//不一致时平衡两堆
max_heap.offer(min_heap.poll());
}
}
public Double GetMedian() {
int min_top = min_heap.peek();
return min_heap.size() > max_heap.size() ? min_top*1.0 : (min_top + max_heap.peek())/2.0;
}
}