一看到找某一数组中的中位数我们就很容易想到的方法:将数组排序,然后直接利用数组的长度÷2就可以获得中位数了。
比如说给你一个数组 arr=[1,6,3,8,2,9,14,5];让你找到他的中位数,我们很容易想到以下代码:
public int findMidNum(int[] arr){
Arrays.sort(arr);//默认对数组进行从小到大排序
return arr[arr.length/2];
}
但是如果我们在找中位数的过程中,又不断的向这个数据流中添加数据呢,并且这个查找的方法我们可能会调用上百遍、上千遍或者更多遍呢?
- 我们可以仔细想一下,如果我们每次在插入的过程中都能够保证这个数据流是从小到大的顺序排列呢?(为了更好理解,我们这里称这个数据流是一个可变数组)也就是说每次在插入数据的时候我们需要现在数组中找到一个合适的位置,这个位置前面的数要比即将插入的数据小,这个位置后面的数据要比即将插入的数据大。那我们不能一下子遍历整个数组,从数组的第一个开始,一个一个慢慢地往后找吧,这里就应该想到我们的二分查找这里我们就以leetcode上的题目来做讲解吧。
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位
于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
class MedianFinder {
List<Integer> list;
public MedianFinder() {
list = new ArrayList();
}
public void addNum(int num) {
//一上来需要判断这个数组里面是不是空的,如果是空的,直接插入
if(list.size() == 0){
list.add(num);
}else{
//以下就是二分查找的常规操作
int start = 0;
int end = list.size()-1;
int mid = (start + end) >> 1;
while(start <= end){
mid = (start + end) >> 1;
//注意这里为了需要跳出的时刻,因为后面适合mid+1位置比较了的,负责会数组越界
if(mid == list.size()-1){
break;
}
//这里就是找到合适的位置插入数据,需要写“=”,因为这个数据可能恰好和前一个或者后一个相等
if(list.get(mid) <= num && list.get(mid+1) >= num){
list.add(mid+1,num);
return;
}else if(list.get(mid) > num){
end = mid - 1;
}else if(list.get(mid) < num){
start = mid + 1;
}
}
//这里是处理边界时候的情况,同时再插入的时候也是要比较这个位置上的数据和即将插入的数据的大小
if(mid == list.size() - 1){
if(num >= list.get(mid)){
list.add(num);
}else{
list.add(mid,num);
}
return;
}else if(mid == 0){
if(num > list.get(0)){
list.add(1,num);
}else{
list.add(0,num);
}
return;
}
}
}
public double findMedian() {
int count = list.size();
if(count % 2 != 0){
return (double)list.get(count/2);
}else{
//这里注意不要用double强转了;举个例子
//例如:3/2=1;3/2.0=1.5;显然前者如果用double也回得到1;
return (list.get(count/2)+list.get(count/2-1))/2.0;
}
}
}
- 接下来我们继续想,既然每次都需要排序,那我们是不是可以尝试用优先队列呢?但是这里优先队列需要有一个技巧,就是在利用一个最大堆来存储中间位置的左边数据,利用一个最小堆来存储中间位置右边的数据。
class MedianFinder {
Queue<Integer> Big_queue;//设置为最大堆
Queue<Integer> Low_queue;//设置为最小堆
int flag = 0;
/** initialize your data structure here. */
public MedianFinder() {
//这边我利用了Compatator接口来设置降序还是升序,大家也可以自己写一个接口实现类
Low_queue = new PriorityQueue(new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o1.compareTo(o2);
}
});
Big_queue = new PriorityQueue(new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o2.compareTo(o1);
}
});
}
public void addNum(int num) {
//首先往最大堆里面存数据
Big_queue.add(num);
//然后将最大堆里面的最大的数据弹出放到最小堆里面,记住,最大堆里面的数据不一定是最小堆里面最大的,
//所以我们此处只是借用最大堆和最小堆来进行排序;
Low_queue.add(Big_queue.poll());
//这一步需要大家领会一下,因为我们一开始是往最大堆里存数据的,我们需要设计这一步让最小堆里面的数据弹出到最大堆里;
if(Low_queue.size() > Big_queue.size()){
Big_queue.add(Low_queue.poll());
}
}
public double findMedian() {
if(Big_queue.size() == Low_queue.size()){
return (Big_queue.peek() + Low_queue.peek())/2.0;
}
return (double)Big_queue.peek();
}
}