295. 数据流的中位数

· 约 2 分钟阅读 · 次阅读 leetcode

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

例如,

[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 范围内,你将如何优化你的算法?


两个堆

时间复杂度主要是优先队列调整的时间O(logn) 空间复杂度O(n)

class MedianFinder {
    // 建立一个大顶堆 一个小顶堆,大顶堆存储小于中位数的数,小顶堆存储大于中位数的数
    // 如 bigHeap存储1-100,smallHeap存储101-200
    // 保证大顶堆的数比小顶堆的数多的不超过1
    priority_queue<int> bigHeap;
    priority_queue<int, vector<int>, greater<int>> smallHeap;
public:
    MedianFinder() {

    }
    
    void addNum(int num) {
        if (bigHeap.empty()) {
            bigHeap.emplace(num);
            return;
        }
        if (num >= bigHeap.top()) {
            smallHeap.emplace(num);
            if (smallHeap.size() > bigHeap.size()) {
                bigHeap.emplace(smallHeap.top());
                smallHeap.pop();
            }
        }else{
            bigHeap.emplace(num);
            if (bigHeap.size() > smallHeap.size()+1){
                smallHeap.emplace(bigHeap.top());
                bigHeap.pop();
            }
        }
    }
    
    double findMedian() {
        // 左右堆的大小一样,即数的个数为偶数
        if (bigHeap.size() == smallHeap.size()){
            return bigHeap.top() + (smallHeap.top()-bigHeap.top())/2.0;
        }else{
            return bigHeap.top();
        }

    }
};

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

有序集合+双指针

时间复杂度同样是O(logn)

class MedianFinder {
    multiset<int> nums;
    multiset<int>::iterator left, right;
public:
    MedianFinder() : left(nums.end()), right(nums.end()) {}
    
    void addNum(int num) {
        auto n = nums.size();
        nums.insert(num);
        if (n == 0) {
            left = right = nums.begin();
        }else if (n&1) {     //集合元素个数原本为奇数
            if (num >= *right){
                right++;
            }else{
                left--;
            }
        }else{      //原本为偶数,这时再加一个数,则left和right会指向中间同一个数
            // 有序集合的特点是,如果数相等,新数会在旧数的右边
            if (num >= *right){
                left++;
            }else{
                right--;
                left = right;
            }
        }
        
    }
    
    double findMedian() {
        return (*left+*right)/2.0;
        

    }
};

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

进阶 11 如果数据流中所有整数都在 00 到 100100 范围内,那么我们可以利用计数排序统计每一类数的数量,并使用双指针维护中位数。

进阶 22 如果数据流中 99%99% 的整数都在 00 到 100100 范围内,那么我们依然利用计数排序统计每一类数的数量,并使用双指针维护中位数。对于超出范围的数,我们可以单独进行处理,建立两个数组,分别记录小于 00 的部分的数的数量和大于 100100 的部分的数的数量即可。当小部分时间,中位数不落在区间 [0,100][0,100] 中时,我们在对应的数组中暴力检查即可。