题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
解题思路:
最朴素的想法是,通过设定一个ArrayList链表,在插入函数中,每次通过add函数添加num到当前状态下的链表中。
在GetMedian函数中,由于数据是随着数据流不断更新的,所以需要先使用Collections类对链表集合进行排序,之后根据当前数据的数量的奇偶性判断输出结果。算是暴力解法,一定要注意的是在java中,使用位运算作为判定整型变量奇偶性时,需要给位运算的过程添加括号,不然编译会报错,这一点和C++不同。代码如下:
import java.util.ArrayList ;
import java.util.Collections ;
public class Solution {
public ArrayList<Integer> ans = new ArrayList<>() ;
public void Insert(Integer num) {
ans.add(num) ;
}
public Double GetMedian() {
double temp = 0.0 ;
Collections.sort(ans) ;
int len = ans.size() ;
if((len & 1) == 0) {
temp = (ans.get(len/2) + ans.get(len/2-1)) / 2.0 ;
} else {
temp = Double.valueOf(ans.get(len/2)) ;
}
return temp ;
}
}
重点来了,另外一种不朴素的做法,大根堆和小根堆做法,即使用优先队列维护数据流组成的数据集合的前后两个堆,作为中位数,前面维护的是一个大根堆,即中位数大于前面集合的所有数值,后面维护的是一个小根堆,即中位数小于后面集合的所有数值。
1.那么再考虑一下细节:我们设定一个计数的变量cur,当一个数据到来时,cur ++。之后判断数据集合的长度是偶数还是奇数。
如果为奇数,那么使用大根堆来存值,故遇到奇数时查询中位数,直接弹出大根堆的堆顶元素即中位数,否则弹出大小根堆堆顶元素取平均数即可。
2.那么再考虑一下,当奇数的元素进入大根堆之前,如果该元素大于右边的小根堆元素,那么整个数据的集合就不能保证顺序排列了,结果不是我们期望的。
因此在进入大根堆之前需要先将其放入右边小根堆,之后从小根堆弹出最小的值放入大根堆,同理在偶数的数据到来时,先进入大根堆,之后弹出最大值,放入小根堆即可。
还有一个点需要注意:优先队列默认定义的是小根堆。因此需要自定义实现一下Comparator类接口,并覆写compare函数的排序规则,以使得队列降序输出,每次获得队列中的最大元素。
代码如下:
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.PriorityQueue ;
public class Solution {
//public ArrayList<Integer> ans = new ArrayList<>() ;
public PriorityQueue<Integer> min = new PriorityQueue<>() ;
public PriorityQueue<Integer> max = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1 ;
}
}) ;
public int cur = 0 ;
public void Insert(Integer num) {
++ cur ;
if((cur & 1) == 1) {
min.offer(num) ;
int temp = min.poll() ;
max.offer(temp) ;
} else {
max.offer(num) ;
int temp = max.poll() ;
min.offer(temp) ;
}
return ;
}
public Double GetMedian() {
double ans = 0.0 ;
if((cur & 1) == 1) {
ans = Double.valueOf(max.peek()) ;
} else {
ans = (min.peek() + max.peek()) / 2.0 ;
}
return ans ;
}
}