面试题05:数据流中的中位数(C++)

题目地址:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/

题目描述

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

例如,

[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]

解题思路

思路1:简单排序,我们使用数组arr用于存储添加的数,然后对数组进行排序,返回中位数即可,但可惜的是C++中超时。

思路2:二分查找插入,这是对简单排序的优化,使得每次在插入数之前,数组已经排好序,这样就减少了排序这一操作,降低了时间复杂度,那么,如何实现在插入数之前,数组就已经排好序了呢?我们使用二分查找寻找合适的位置,并将该数插入此次,具体就是利用函数lower_bound()找到数组arr范围内不小于num的数的范围,然后在该位置前使用insert()函数插入num。

lower_bound 查找的是第一个大于等于当前查找数的位置;

upper_bound 查找的是第一个严格大于当前查找数的位置。

思路3:优先队列(堆):我们使用大顶堆和小顶堆分别存储小于等于中位数的数和大于中位数的数。若数组长度为奇数,则中位数是最中间的那个,若数组长度为偶数,则中位数是中间偏左的那个元素。在大顶堆中,我们只需要poll一次,便可弹出当前的中位数,为了获得中位数,必须维持大顶堆和小顶堆的平衡,使得大顶堆的大小总是要比小顶堆大于等于1。

大顶堆和小顶堆的实现代码参考文章

https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/quan-shou-xie-dui-pai-xu-jiu-shi-shi-jian-bi-jiao-/

程序源码

简单排序(超时)

class MedianFinder {
public:
    /** initialize your data structure here. */
    vector<double> arr;
    MedianFinder() {

    }
    
    void addNum(int num) {
        arr.push_back(num);
    }
    
    double findMedian() {
        sort(arr.begin(), arr.end());
        int n = arr.size();
        if(n % 2 == 0) return (arr[n / 2 - 1] + arr[n / 2]) / 2;
        else
            return arr[n / 2];
    }
};

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

二分查找插入

class MedianFinder {
public:
    /** initialize your data structure here. */
    vector<double> arr;
    MedianFinder() {

    }
    
    void addNum(int num) {
        if(arr.empty()) arr.push_back(num);
        else
            arr.insert(lower_bound(arr.begin(), arr.end(), num), num); //lower_bound函数可替代auto index = lower_bound(arr.begin(),arr.end(),num);即arr.insert(index,num);
    }
    
    double findMedian() {
        int n = arr.size();
        if(n % 2 == 0) return (arr[n / 2 - 1] + arr[n / 2]) / 2;
        else
            return arr[n / 2];
    }
};
/*
另外一种加数方法
void addNum(int num) {
        bool flag = true;         for(int i = 0; i < arr.size(); i++)         {             if(arr[i] >= num)             {                 arr.insert(arr.begin() + i, num);                 flag = false;                 break;             }         }         if(flag == true) arr.push_back(num);     }
*/
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

优先队列

class MedianFinder {
public:
    /** initialize your data structure here. */
    priority_queue<int> big_heap;
    priority_queue<int, vector<int>, greater<int>> small_heap;
    MedianFinder() {

    }
    
    void addNum(int num) {
        big_heap.push(num);
        small_heap.push(big_heap.top()); //平衡操作
        big_heap.pop();
        if(big_heap.size() < small_heap.size()) 
        {
            big_heap.push(small_heap.top());
            small_heap.pop();
        }
    }
    
    double findMedian() {
        if(big_heap.size() > small_heap.size()) return (double)big_heap.top();
        else
            return (double)(big_heap.top() + small_heap.top()) / 2;
    }
};

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

参考文章

https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/you-xian-dui-lie-by-z1m/

上一篇:剑指 Offer 41. 数据流中的中位数


下一篇:LeetCode-921 Minimum Add to Make Parentheses Valid Solution (with Java)