目录
数据流中的中位数
描述
如何得到一个数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1
输入
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"] [[],[1],[2],[],[3],[]]
输出
[null,null,null,1.50000,null,2.00000]
示例 2
输入
["MedianFinder","addNum","findMedian","addNum","findMedian"] [[],[2],[],[3],[]]
输出
[null,null,2.00000,null,2.50000]
限制
最多会对 addNum、findMedian 进行 50000 次调用。
方法:双堆
我们将数据分为两个部分:小顶堆A和大顶堆B,分别存储列表一半的元素,并且:
- A存储较大的一半,长度为N/2(N为偶数)或(N+1)/2(N为奇数),记A中元素个数为m
- B存储较小的一半,长度为N/2(N为偶数)或(N-1)/2(N为奇数),记B中元素个数为n
添加元素时,有两种情况:
- 当m=n时(即N为偶数):需要向A中添加一个元素,首先将新元素num插入到B中,然后将B的堆顶元素插入到A中
- 当m!=n时(即N为奇数):需要向B中添加一个元素,首先将新元素num插入到A中,然后将A的堆顶元素插入到B中
查找中位数时,也有两种情况:
- 当m=n时(即N为偶数):中位数为(A的栈顶元素+B的栈顶元素)/2
- 当m!=n时(即N为奇数):中位数为A的栈顶元素
class MedianFinder {
Queue<Integer> A, B;
public MedianFinder() {
A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
}
public void addNum(int num) {
if(A.size() != B.size()) {
A.add(num);
B.add(A.poll());
} else {
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
}
}
因为该方法直接调用了库函数实现的大顶堆和小顶堆,这里我重新实现一下堆排序巩固一下:
public Integer[] MaxHeap(Integer[] arr){
for (int i = arr.length/2-1; i >=0 ; i--) {
adjustMinHeap(arr,i,arr.length);
}
for (int j = arr.length-1; j > 0 ; j--) {
swap(arr,0,j);
adjustMinHeap(arr,0,j);
}
return arr;
}
public void adjustMinHeap(Integer[] arr, int i, int length) {
int temp=arr[i];//存储当前节点的值
for (int k = 2*i+1; k < length; k=2*k+1) {//找到该节点的叶子节点
if (k+1<length && arr[k]<arr[k+1]){//如果右边节点存在,并且比左边节点大,那么优先处理更大的节点
k++;
}
if (arr[k]>temp){//如果叶子节点比当前节点的值大,则交换位置
swap(arr,i,k);
i=k;
}else{
break;
}
}
}
public void swap(Integer[] arr, int a, int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}