剑指Offer(41)数据流中的中位数

本文介绍了如何使用双堆数据结构来实现在数据流中实时计算中位数的方法。通过维护一个小顶堆和一个大顶堆,分别存储数据流中较大的和较小的一半元素,确保在添加新元素时保持堆的平衡。当需要查找中位数时,根据堆的大小判断是取小顶堆的顶部元素还是两个堆顶部元素的平均值。这种方法在处理大量数据时能有效保证中位数的即时计算,并且在代码中给出了具体的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

数据流中的中位数

描述

示例 1

示例 2

限制

方法:双堆


数据流中的中位数

描述

如何得到一个数据流中的中位数?

如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。

如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值