剑指 Offer系列——剑指 Offer 41. 数据流中的中位数(困难题)

✨剑指 Offer 41. 数据流中的中位数✨

力扣C++打卡(12.13)!✊✊✊🌈大家好!本篇文章将继续介绍关于栈队列堆的OJ题,题目来自力扣:剑指 Offer 41. 数据流中的中位数,展示代码语言暂时为:C++代码 😇。


🔒1、题目:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[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 次调用。

来源:力扣(LeetCode)👈
链接:https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/description/

☀️2、思路:

题目要求得到一个数据流中的中位数,可以考虑使用两个优先堆进行实现。 优先堆实现方式:

	priority_queue<int, vector<int>, less<int> > maxheap; 
	priority_queue <int, vector<int>, greater<int> > minheap;

本题使用一个大顶堆和一个小顶堆完成。大顶堆的每个节点的值大于等于左右孩子节点的值,堆顶为最大值。小顶堆的每个节点的值小于等于左右孩子节点的值,堆顶为最小值。
使用大顶堆(maxHeap) 来存储数据流中较小一半的值,用小顶堆(minHeap) 来存储数据流中较大一半的值。即“大顶堆的堆顶”与“小顶堆的堆顶”就是排序数据流的两个中位数。maxHeap堆底 <= maxHeap堆顶 <= minHeap堆顶 <= minHeap堆底。

举个例子若输入的数据个数是奇数,比如 3、6、4、5、7。我们可以把左边的3、4 存入一个大顶堆中,把右边的 5、6、7 存入一个小顶堆中。那么中位数就是小顶堆的 top()。
若输入的数据个数是偶数,比如 3、6、4、5。我们可以把左边的 3、4 存入一个大顶堆中,把右边的 5、6存入一个小顶堆中。那么中位数就是两个堆的 top() 的和再乘 0.5。
整个过程我们需要维护两个地方:两个堆的 size() 最大只能相差 1;大顶堆的 top() 必须小于等于小顶堆的 top()。

实现流程:
1️⃣ 每插入一个数字,均对两个堆的size()进行比较;
2️⃣ 若相等,先将这个数插入大顶堆,然后将大顶堆的 top() 插入小顶堆。这么做可以保证小顶堆的所有数永远大于等于大顶堆的 top()。
3️⃣ 若不相等,先将这个数插入小顶堆,然后将小顶堆的 top() 插入大顶堆。这么做可以保证大顶堆的所有数永远小于等于小顶堆的 top()。
4️⃣ 若最后两个堆的 size() 相等。那么中位数就是两个堆的 top() 的和再乘 0.5。若最后两个堆的 size() 不等。由于当两个堆的 size() 相等时我们总是选择将数插入小顶堆中(先插入大顶堆但马上又将大顶堆的 top() 插入小顶堆),所以中位数一定是小顶堆的 top()✔️。

原作者:superkakayong
链接:superkakayong👈

复杂度分析:
⏳时间复杂度 O(N) :每次存放都会进行push和pop操作,时间复杂度为 O(N)。
🏠空间复杂度 O(N) :数据流长度为N,两个堆一个存入N个数据。

🔑3、代码:
class MedianFinder {
public:
    priority_queue<int, vector<int>, less<int> > maxheap;
    priority_queue<int, vector<int>, greater<int> > minheap;
    MedianFinder() {
        
    }
    
    void addNum(int num) {
        if(maxheap.size() == minheap.size()){
            maxheap.push(num);
            minheap.push(maxheap.top());
            maxheap.pop();
        }
        else if(maxheap.size() < minheap.size()){
            minheap.push(num);
            maxheap.push(minheap.top());
            minheap.pop();
        }
    }
    
    double findMedian() {
        int maxSize = maxheap.size(),minSize = minheap.size();
        int mid1 = maxheap.top(),mid2 = minheap.top();
        return maxSize==minSize ? 0.5*(mid1+mid2):mid2;
    }
};
📚4、补充思路:

题目要求得到一个数据流中的中位数,借鉴昨天学习的快速排序思想,本文可以使用哨兵进行划分,每想插入一个数num时,选择当前数据流中间位置的数为基准数,进行哨兵划分将数据流划分成小于中位数的数,和大于中位数的数。为了简化,仅对大于中位数的数进行排序,小于中位数的直接存放到首元素。这样数据流中间位置的1个数或者两个数的平均数一直为中位数。

代码实现如下:

class MedianFinder {
public:
    MedianFinder() {

    }
    vector<int> de;
    void addNum(int num) {
        int l = 0;
        int r = de.size();
        while(l<r){
            int m = l+(r-l)/2;
            if(num>de[m]){
                l = m+1;
            }
            else{
                r = m;
            }
        }
        de.insert(de.begin()+l,num);
    }
    
    double findMedian() {
        int n = de.size();
        if(n%2==1){
            return (double)de[n/2];
        }
        else{
            return ((double)de[n/2]+(double)de[n/2-1])/2;
        }
    } 
};
🐾5、总结

🌈 今天题目较难,求数据流中的中位数,采用了两种方法。
第一种,用两个优先堆进行处理,重点是要动态维持两个堆中元素个数差不大于1
第二种采用昨天学习到的 快速排序 的思想,选用数据流中间位置的数对数据进行划分,这样能很好地保证中间位置的数为中位数。
坚持打卡,继续加油!✊✊

博主水平有限,如果有疑问的可以评论区留言交流!
🚀🚀觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦🙏 🙏🙌 !

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君莫笑lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值