查找中位数
数组
用一个数组记录所有addNum
添加进来的数字,通过插入排序的逻辑保证数组中的元素有序,当调用findMedian
方法时,可以通过数组索引直接计算中位数。
但是用数组作为底层容器的问题也很明显,addNum
搜索插入位置的时候可以用二分搜索算法,但是插入操作需要搬移数据,所以最坏时间复杂度为 O(N)。
链表
链表插入元素很快,但是查找插入位置的时候只能线性遍历,最坏时间复杂度还是 O(N),而且findMedian
方法也需要遍历寻找中间索引,最坏时间复杂度也是 O(N)。
TreeSet
TreeSet
是一种Set
,其中不存在重复元素的元素,但是数据流可能输入重复数据。
TreeSet
并没有实现一个通过排名快速计算元素的 API。假设想找到TreeSet
中第 5 大的元素,并没有一个现成可用的方法实现这个需求。
优先队列
定义两个优先队列,大顶堆small
和小顶堆large
。想象一下,small
中存放的是整个有序数组的前半部分,小顶堆large
存放的是整个有序数组的后半部分,如下图所示,那么中位数为堆顶的元素,两个堆大小相等则求平均,否则取堆较大的堆顶。(图片出自微信公众号:labuladong)
用两个堆来求解中位数挺有意思的,精髓是addNum
方法。
class MedianFinder {
PriorityQueue<Integer> small;
PriorityQueue<Integer> large;
public MedianFinder() {
// 定义大顶堆
small = new PriorityQueue<>((a,b)->{return b-a;});
// Java默认是小顶堆
large = new PriorityQueue<>();
}
public void addNum(int num) {
if (small.size()>=large.size()){
small.add(num);
large.add(small.poll());
}else{
large.add(num);
small.add(large.poll());
}
}
public double findMedian() {
if (large.size()==small.size())
return (large.peek()+small.peek())/2.0;
else
return large.size()>small.size()?large.peek():small.peek();
}
}
用两个堆不仅可以用来求中位数,也可以求第k位数。