题目:数据流中的中位数
获取数据流中的中位数。如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
主要思路:
可以把输入数据平分成两部分,左边的数据都小于右边的数据,那么即使左右两边内部的数据没有排序,也可以根据左边最大的数和右边最小的数获取中位数。关键是,怎么获取左边的最大值和右边的最小值,并且保持左右两部分的个数之差不超过1?
可以借助最大堆和最小堆来实现,同时轮流把数据添加到左右部分。为了保证最小堆(右半部分)的所有元素都大于最大堆(左半部分)的所有元素,若当前数字本来要添加到最小堆中,但是当前数字比最大堆的最大值还小,那么,先把该数字添加到最大堆中,再把最大堆中的最大数字移出到最小堆中,这样就能保证最小堆中的所有元素都大于最大堆中的所有元素;若当前数字本来要添加到最大堆中,但是当前数字比最小堆的最小值还大,类似的,先添加该数字到最小堆,再移出最小堆的最小数字到最大堆。
时间复杂度:O(log(n))
//思路:左大根堆,右小根堆
PriorityQueue<Integer> leftMax = new PriorityQueue<>(
new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
PriorityQueue<Integer> rightMin = new PriorityQueue<>();
//注:应该均匀插入,先插左边,再插右边
public void Insert(Integer num) {
//应该插左边
if(leftMax.size() == rightMin.size()) {
//但是待插入值比右边的值大,则需要先插入右边,再取右边的最小值插入左边
if(!rightMin.isEmpty() && num > rightMin.peek()) {
rightMin.offer(num);
Integer min = rightMin.poll();
leftMax.offer(min);
}else {
leftMax.offer(num);
}
//应该插入右边
}else {
//但是待插入的值比左边的最大值小,则需要先插入左边,
//再取左边的最大值插入右边
if(!leftMax.isEmpty() && num < leftMax.peek()) {
leftMax.offer(num);
Integer max = leftMax.poll();
rightMin.offer(max);
}else {
rightMin.offer(num);
}
}
}
public Double GetMedian() {
//如果左边数目等于右边数目:各取顶点求均值
if(leftMax.size() == rightMin.size()) {
return (leftMax.peek() + rightMin.peek()) / 2.0;
}
//左边数目比较多,则取左边顶点
return new Double(leftMax.peek());
}