295. 数据流的中位数

295. 数据流的中位数

欢迎大家参加每日一题系列并提供其他版本,在你也可以按照相同格式提供你的最新的每日一题题解。
每日一题系列

题目描述:

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3) 
findMedian() -> 2

进阶:

  1. 如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?

  2. 如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

解答:

C++

方法一:暴力

暴力接法:直接尾部插入,sort快速排序,取中位数

class MedianFinder {
public:
    /** initialize your data structure here. */
    vector<int> mem_num;
    MedianFinder() {   
    }
    
    void addNum(int num) {
        this->mem_num.push_back(num);//插入
        sort(this->mem_num.begin(),this->mem_num.end());//快排
    }
    
    double findMedian() {
        int len=this->mem_num.size();
        //cout<<len<<endl;
        double mid_num=0;
        if(len%2==0){
            mid_num=(double(this->mem_num[len/2])+double(this->mem_num[len/2-1]))/2;
        }else if(len%2!=0){
            mid_num=double(this->mem_num[len/2]);
        }

        return mid_num;//中位数
    }
};

优化:二分插入,取中位数

class MedianFinder {
public:
    /** initialize your data structure here. */
    vector<int> mem_num;
    MedianFinder() {   
    }
    
    void addNum(int num) {
        auto addr=upper_bound(mem_num.begin(),mem_num.end(),num);
        mem_num.insert(addr,num);
    }
    
    double findMedian() {
        int len=this->mem_num.size();
        //cout<<len<<endl;
        double mid_num=0;
        if(len%2==0){
            mid_num=(double(this->mem_num[len/2])+double(this->mem_num[len/2-1]))/2;
        }else if(len%2!=0){
            mid_num=double(this->mem_num[len/2]);
        }

        return mid_num;
    }
};

方法二:优先队列

将数据分为两个优先队列(MIN与MAX)分别记录大于中位数的数和小于等于中位数的数,优先队列中的每个元素都有优先级,而优先级高(或者低)的将会先出队,而优先级相同的则按照其在优先队列中的顺序依次出队。

优先队列往往使用堆来实现

思路:First_num优先分配给MIN优先队列,New_num大于MIN.top()分配给MAX,若MAX.size()>MIN.size()把MAX.top()给MIN使两个优先队列的队头处于中位数位置;New_num小于等于MIN.top()分配给MIN,若MAX.size()<MIN.size()+1把MIN.top()给MAX使优使两个优先队列的队头处于中位数位置;返回队头即中位数

std::priority_queue默认由大根堆实现,即最大的数在堆顶,看下图:

image-20210827153753303.png (1131×678) (jsdelivr.net)

class MedianFinder {
public:
    priority_queue<int> MIN;
    priority_queue<int, vector<int>,greater<int>> MAX;

    MedianFinder() {}

    void addNum(int num) {
        if (MIN.empty() || num <= MIN.top()) {
            MIN.push(num);
            if (MAX.size() + 1 < MIN.size()) {
                MAX.push(MIN.top());
                MIN.pop();
            }
        } else {
            MAX.push(num);
            if (MAX.size() > MIN.size()) {
                MIN.push(MAX.top());
                MAX.pop();
            }
        }
    }

    double findMedian() {
        if (MIN.size() > MAX.size()) {
            return MIN.top();
        }
        return (MIN.top() + MAX.top()) / 2.0;
    }
};

方法三:有序集+双指针

使用multiset有序集,并维护两个指针分别指向中位数的左右两个数,下程序指针维护未看懂,可以观察实例理解;

class MedianFinder {
    multiset<int> nums;//创建有序集
    multiset<int>::iterator left, right;//创建中位数的双指针

public:
    MedianFinder() : left(nums.end()), right(nums.end()) {}

    void addNum(int num) {
        int n = nums.size();//n为未插入大小
        nums.insert(num);//插入数据
        if (!n) {//有序集为空都指向头部
            left = right = nums.begin();
        } else if (n & 1) {//判断n为奇偶数,n奇数
            if (num < *left) {//此时,left与right指向同一个数,此时size()为偶数,新数小于左指针指向的数,所以left——;
                left--;
            } else {//此时,left与right指向同一个数,此时size()为偶数,新数大于左指针指向的数,所以right++;
                right++;
            }
        } else {//n为偶数
            if (num > *left && num < *right) {//此时,left与right指向左右两个数,此时size()为奇数,新数大于左指针指向的数,且小于右指针指向的数所以left++,right--,左右指向同一处;
                left++;
                right--;
            } else if (num >= *right) {//此时,left与right指向左右两个数,此时size()为奇数,新数大于右指针指向的数所以left++,左右指向同一处;
                left++;
            } else {//此时,left与right指向左右两个数,此时size()为奇数,新数小于左指针指向的数所以 right--,左右指向同一处;
                right--;
                left = right;
            }
        }
    }

    double findMedian() {
        return (*left + *right) / 2.0;
    }
};

欢迎大家参加每日一题系列并提供其他版本,在你也可以按照相同格式提供你的最新的每日一题题解。
每日一题系列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值