LeetCode 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
进阶:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-median-from-data-stream
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、解题思路

      按照题目要求,最简单的方法是维持一个动态数组,在每次执行插入操作时将元素num保存到动态数组中;在每次findMedian时先将数组排序,再查找中位数返回结果。该方法超时,本文不进行介绍。下面介绍其他两种解决方法:

(1)两个堆的方法,即维护一个最大堆和一个最小堆,最大堆中保存小于等于中位数的元素,最小堆中保存大于中位数的元素。如果数组的长度n为奇数时,最大堆的堆顶即为结果;如果n为偶数,最大堆和最小堆的堆顶元素的平均值即为结果。该方法的难点在于怎么平衡两个堆中元素的个数,平衡方法请见代码。

(2)Multiset 和双指针,利用multiset(内部的数据结构为自平衡二进制搜索树)自动排序的功能。我们保持两个指针:一个用于中位数较低的元素,另一个用于中位数较高的元素。当元素总数为奇数时,两个指针都指向同一个中值元素(因为在本例中只有一个中值)。当元素数为偶数时,指针指向两个连续的元素,其平均值是输入的代表中位数。其中两个指针的更新方式,请见代码。

三、解题代码

(1)两个堆的方法
class MedianFinder {
private:
    priority_queue<int> low; //最大堆 保存小于等于中位数的元素
    priority_queue<int, vector<int>, greater<int>> high; //最小堆 保存大于中位数的元素
public:
    /** initialize your data structure here. */
    MedianFinder() {
        
    }
    
    void addNum(int num) {
        low.push(num); //元素首先存入到最大堆中

        high.push(low.top()); //将最大堆中堆顶元素保存到最小堆中
        low.pop();

        if(low.size() < high.size()) //要保持最大堆元素的个数大于等于最小堆的元素,保持平衡
        {
            low.push(high.top());
            high.pop();
        }
    }
    
    double findMedian() {
        //如果元素为2*n + 1个,则最大堆中存入n+1个,最小堆中存入n个
        //如果元素为2*n,则最大堆中存入n个,最小堆存入n个
        return low.size() > high.size() ? low.top() : (low.top() + high.top()) * 0.5;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */
(2)multiset + 双指针方法
class MedianFinder {
private:
    multiset<int> data;
    multiset<int>::iterator l_mid, r_mid;
public:
    /** initialize your data structure here. */
    MedianFinder():l_mid(data.end()), 
                   r_mid(data.end()) {
        
    }
    
    void addNum(int num) {
        int n = data.size();
        data.insert(num);
        if(n == 0) //如果哈希集合为空,两个指针指向同一个元素
        {
            l_mid = data.begin();
            r_mid = data.begin();
        }
        else if(n & 1) //n为奇数时,插入num之后变为偶数
        {
            if(num < *l_mid) //num插入到小于中位数的一边
            {
                l_mid--;
            }
            else  //num插入到大于等于中位数的一边
            {
                r_mid++;
            }
        }
        else   //n为偶数时,插入num之后变为奇数
        {
            if(num > *l_mid && num < *r_mid) //num的值介于两个中位数之间
            {
                l_mid++;
                r_mid--;
            }
            else if(num >= *r_mid) //num插入到大于等于右中位数的一边
            {
                l_mid++;
            }
            else    // num <= *l_mid < *r_mid 注意当num == *l_mid时,num插入到l_mid和r_mid的中间
            {
                l_mid = --r_mid; //保持*l_mid不变且更新了r_mid所指向的元素
            }
        }
    }
    
    double findMedian() {
        return (*l_mid + *r_mid) * 0.5;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

四、解题结果

(1)两个堆的方法

(2)multiset + 双指针方法

发布了139 篇原创文章 · 获赞 122 · 访问量 4740

猜你喜欢

转载自blog.csdn.net/qq_39661206/article/details/103433739
今日推荐