参考自:《剑指Offer——名企面试官精讲典型编程题》
题目:数据流中的中位数
获取数据流中的中位数。如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
主要思路:可以把输入数据平分成两部分,左边的数据都小于右边的数据,那么即使左右两边内部的数据没有排序,也可以根据左边最大的数和右边最小的数获取中位数。关键是,怎么获取左边的最大值和右边的最小值,并且保持左右两部分的个数之差不超过1?
可以借助最大堆和最小堆来实现,同时轮流把数据添加到左右部分。为了保证最小堆(右半部分)的所有元素都大于最大堆(左半部分)的所有元素,若当前数字本来要添加到最小堆中,但是当前数字比最大堆的最大值还小,那么,先把该数字添加到最大堆中,再把最大堆中的最大数字移出到最小堆中,这样就能保证最小堆中的所有元素都大于最大堆中的所有元素;若当前数字本来要添加到最大堆中,但是当前数字比最小堆的最小值还大,类似的,先添加该数字到最小堆,再移出最小堆的最小数字到最大堆。
关键点:最小堆,最大堆,平分数据(轮流添加)
时间复杂度:O(log(n))
public class MedianNumber
{
private static Queue<Integer> minHeap = new PriorityQueue<>();
private static Queue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
private static boolean isOddCount = false; //数据总数目是奇数
public static void main(String[] args)
{
insert(5);
System.out.println(getMedian()); //5.0
insert(2);
System.out.println(getMedian()); //3.5
insert(3);
System.out.println(getMedian()); //3.0
insert(4);
System.out.println(getMedian()); //3.5
}
private static void insert(Integer currentNum)
{
//左边为最大堆,右边为最小堆
//要维持最小堆的所有元素都大于最大堆的所有元素
//数据总数为奇数,则添加到最大堆
if (isOddCount)
{
//当前数值比最小堆的最小值大,则先添加到最小堆,
//再取出最小堆的最小值添加到最大堆
if (minHeap.size() > 0 && currentNum > minHeap.peek())
{
minHeap.offer(currentNum);
currentNum = minHeap.poll();
}
maxHeap.offer(currentNum);
}
//数据总数为偶数,则添加到最小堆
else
{
//当前数值比最大堆的最大值小,则先添加到最大堆,
//再取出最大堆的最大值添加到最小堆
if (maxHeap.size() > 0 && currentNum < maxHeap.peek())
{
maxHeap.offer(currentNum);
currentNum = maxHeap.poll();
}
minHeap.offer(currentNum);
}
isOddCount = !isOddCount; //总数奇偶性改变
}
private static Double getMedian()
{
int allCount = minHeap.size() + maxHeap.size();
if (allCount == 0) return 0.0;
//数据总数为偶数,则中位数为中间两个数的均值
if ((allCount & 1) == 0)
{
return (minHeap.peek() + maxHeap.peek()) / 2.0;
} else
{
//最小堆元素个数比最大堆的元素个数多1个
return (double) minHeap.peek();
}
}
}