【剑指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)。