【剑指Offer-时间效率】面试题41:数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

思路1

将数组作为容器,当读取中位数时,先将数组升序排序,然后根据数据中元素的个数是奇数还是偶数返回即可。代码如下:

class Solution {
public:
    
    vector<double> v;
    
    void swap(int i, int j)
    {
        double t = v[i];
        v[i] = v[j];
        v[j] = t;
    }
    
    int partition(int left, int right)
    {
        int i = left;
        int j = right;
        double base = v[i];
        while(i<j)
        {
            while(v[j]>=base && i<j)
                j--;
            while(v[i]<=base && i<j)
                i++;
            swap(i, j);
        }
        swap(left, i);
        return i;
    }
    
    void quicksort(int left, int right)
    {
        if(left<right)
        {
            int idx = partition(left, right);
            quicksort(left, idx-1);
            quicksort(idx+1, right);
        }
    }
    
    void Insert(int num)
    {
        v.push_back(num);
    }

    double GetMedian()
    { 
        quicksort(0, v.size()-1);
        if(v.size()%2!=0)
            return v[v.size()/2];
        else
            return (v[v.size()/2]+v[v.size()/2-1])/2;
    }

};

代码使用了快速排序,时间复杂度为O(nlogn)。

思路2

思路2就是书上的方法,用堆来做。容器被中位数分为两部分,左半部分小于中位数,右半部分大于中位数,则可以使用最大堆存储左半部分,使用最小堆存储右半部分,最小堆中的元素大于最大堆中的元素。由于要保持左右元素个数差值不超过1,不妨将第偶数个数(从0开始)插入到最小堆,第奇数个数插入到最大堆,这样中位数或者是最大堆和最小堆堆顶元素和除以2,或者是最小堆的堆顶元素。
当插入的时候,会出现两种情况:第一、当前元素要插入到最小堆,但是该元素小于最大堆的堆顶元素,如果直接插入最小堆,则不能保证最小堆的元素均大于最大堆中的元素。此时,可以先将该元素插入到最大堆当中,然后将最大堆的堆顶元素插入到最小堆。第二、当前元素要插入到最大堆,但是该元素大于最小堆的堆顶元素。此时,先将该元素插入到最小堆,然后将最小堆的堆顶元素插入到最大堆。使用vector、push_heap和pop_heap来实现堆,代码如下:

class Solution {
public:
    
    vector<double> minHeap;
    vector<double> maxHeap;
    
    void Insert(int num)
    {
        if((minHeap.size()+maxHeap.size())%2==0)    //第偶数个元素插入最小堆
        {
            if(maxHeap.size()>0 && num<maxHeap[0])
            {
                maxHeap.push_back(num);
                push_heap(maxHeap.begin(), maxHeap.end(), less<double>());
                num = maxHeap[0];
                pop_heap(maxHeap.begin(), maxHeap.end(), less<double>());
                maxHeap.pop_back();
            }
            minHeap.push_back(num);
            push_heap(minHeap.begin(), minHeap.end(), greater<double>());
        }
        else    //第奇数个元素插入最大堆
        {
            if(minHeap.size()>0 && num>minHeap[0])
            {
                minHeap.push_back(num);
                push_heap(minHeap.begin(), minHeap.end(), greater<double>());
                num = minHeap[0];
                pop_heap(minHeap.begin(), minHeap.end(), greater<double>());
                minHeap.pop_back();
            }
            maxHeap.push_back(num);
            push_heap(maxHeap.begin(), maxHeap.end(), less<double>());
        }
    }

    double GetMedian()
    { 
        int length = minHeap.size()+maxHeap.size();
        if(length%2==0)
            return (minHeap[0]+maxHeap[0])/2;
        else return minHeap[0];
    }

};

向堆中插入一个元素的时间复杂度为O(logn),获取堆顶元素的时间复杂度为O(1),所以获取中位数的时间复杂度为O(1)。

posted @ 2020-03-16 23:32  Flix  阅读(98)  评论(0编辑  收藏  举报