题目描述
如何得到一个数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
思路1
维护一个数组,每次加入后,进行排序,当总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。
思路2
Java 1.5版本后就提供了一个具备了小根堆性质的数据结构也就是优先队列PriorityQueue。
PriorityQueue的数据结构
PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的结果刚好是一个数组。
PriorityQueue的操作
①add(E e) 和 offer(E e) 方法
add(E e) 和 offer(E e) 方法都是向PriorityQueue中加入一个元素
② poll() 和 remove() 方法
poll 方法每次从 PriorityQueue 的头部删除一个节点,也就是从小顶堆的堆顶删除一个节点,而remove()不仅可以删除头节点而且还可以用 remove(Object o) 来删除堆中的与给定对象相同的最先出现的对象。
PriorityQueue实现大顶堆
PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆。如下代码实现了一个初始大小为11的大顶堆:
private static final int DEFAULT_INITIAL_CAPACITY = 11;
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(DEFAULT_INITIAL_CAPACITY, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
这个题另外一种思路就是通过维护一个大顶堆,一个小顶堆,且保证两点:
- 小顶堆里的元素全大于大顶堆里的元素,即小顶堆里维护的是数据流中后一部分较大值,大顶堆维护的是数据流中前一部分的较小值;
- 两个堆个数的差值小于等于1,元素个数为偶数让两个堆中元素个数相同,为奇数时让小顶堆元素个数比大顶堆多1个。则当总元素个数为奇数时,中位数就是小顶堆堆顶;当总元素个数为偶数时,中位数就是两个堆堆顶平均数。
为了让大顶堆的元素都小于小顶堆中元素,在将元素依次插入两个堆中时需要注意:
- n为偶数时插入元素到小顶堆,因为小顶堆元素都要大于大顶堆,但是新插入的元素不一定比大顶堆元素大,因此要先将元素插入大顶堆,然后利用大顶堆的特点,取出堆顶元素即为最大元素,此时再插入小顶堆!
- n为奇数时同理先插入到小顶堆中,然后取出小顶堆堆顶即最小元素插入到大顶堆中。
这样既保证了小顶堆和大顶堆元素个数相差不大于1,又能保证大顶堆的元素都小于小顶堆中元素。
整体代码
package com.zhumq.leetcode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Scanner;
import org.junit.Test;
public class GetMedian {
ArrayList<Integer> list = new ArrayList<>();
// 大顶堆(直接用lambda表达式)
PriorityQueue<Integer> bigHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
// 小顶堆,并且小顶堆元素都大于大顶堆
PriorityQueue<Integer> smallHeap = new PriorityQueue<>();
// 当前数据流读入的元素个数
int N = 0;
/**
* 解法1:维护两个堆,一个大顶堆、一个小顶堆
*/
public void insert1(Integer num) {
// 插入要保证两个堆存于平衡状态
if (N % 2 == 0) {
// N 为偶数插入到小顶堆,奇数插入大顶堆
// 因为小顶堆元素都要大于大顶堆,但是新插入的元素不一定比大顶堆元素大,因此先将元素插入大顶堆,然后利用大顶堆的特点,取出堆顶元素即为最大元素,此时再插入小顶堆!
bigHeap.offer(num);
smallHeap.offer(bigHeap.poll());
} else {
//同理
smallHeap.offer(num);
bigHeap.offer(smallHeap.poll());
}
N++;
}
/*
* 总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。
*/
public Double getMedian1() {
if (N % 2 == 0) {
return (bigHeap.peek() + smallHeap.peek()) / 2.0;
} else {
return (double) smallHeap.peek();
}
}
/**
* 解法2:维护数组,加入时排序
*/
public void insert2(Integer num) {
list.add(num);
Collections.sort(list);
}
/*
* 总元素个数为奇数时,中位数就是数组中间的元素;当总元素个数为偶数时,中位数就是数组中间元素和前一个元素的平均数。
*/
public Double getMedian2() {
int n = list.size();
if (n % 2 == 0) {
return Double.valueOf((list.get(n / 2) + list.get(n / 2 - 1)) / 2.0);
} else {
return Double.valueOf(list.get(n / 2));
}
}
@Test
public void test1() {
Scanner sc = new Scanner(System.in);
while(sc.hasNext()) {
int len = sc.nextInt();
for(int i = 0;i<len;i++) {
int num = sc.nextInt();
insert1(num);
insert2(num);
System.out.println(getMedian1());
System.out.println(getMedian2());
}
}
sc.close();
}
}
运行结果如下: