描述
如何得到一个数据流中的中位数?如果读出奇数个数值,中位数就是所有数值排序后位于中间的数,如果读出偶数个数值,中位数就是所有数值排序后中间两个数的平均数。我们使用Insert()方法读取数据流,使用GetMedian()方法读取当前数据的中位数
例:
输入:[5,2,3,4,1,6,7,0,8]
返回:[5,3.5,3,3.5,3,3.5,4,3.5,4]
进阶:空间O(n),时间O(nlogn)
解题思路
由题意可知本题要求一边录入数据一边求出已经录入的数据的中位数。
方法一:根据中位数定义,直接使用vector数组排序暴力求解
class Solution {
public:
vector<int>input;
void Insert(int num) {
input.push_back(num);//将录入的数据存入数组
}
double GetMedian() {
sort(input.begin(),input.end());
int size = input.size();
if(size%2==1) return input[(size-1)/2];
else return (input[(size-1)/2]+input[(size-1)/2+1])/2.0;//一定要注意除以2.0
}
};
时间O(nlogn)
方法二:使用两个堆,利用两个堆的堆顶解题。
将读入的数据分为几乎数量相同的两部分,一部分数字小,另一部分大。小的一部分采用大顶堆存放,大的一部分采用小顶堆存放。这样两个堆的堆顶就是整个数据流中,最中间的两个数。当总个数为偶数时,使两个堆的数目相同,则中位数=大顶堆的最大数字与小顶堆的最小数字的平均值;而总个数为奇数时,使小顶堆的个数比大顶堆多一,则中位数=小顶堆的最小数字。
插入的步骤如下:
1.若已读取的个数为偶数(包括0)时,两个堆的数目已经相同,再插入一个数时,应该选一个数插入到小顶堆中,从而实现小顶堆的个数多一。但是,不能直接插到小顶堆,本应该选择一个数加入到小顶堆中,但是必须选一个较大的数放入小顶堆,而插入的这个数不一定符合要求(大顶堆的数不服它),所以这个数要和大顶堆的最大数(先进大顶堆)打一群架,谁赢了(谁大)谁进小顶堆。
2。若已读取的个数为奇数时,小顶堆的个数多一,所以要将某个数字插入到大顶堆中,此时方法与上面类似。新进来的数要和小顶堆的堆顶(最小值)打一架,打输的(更小的那个数)进入大顶堆。
本方法的空间复杂度是O(1),空间复杂度是O(logn),相比于以上几个方法,可以说是最优选择。因此也是大家使用最多的解法。堆有多种方式实现,数组或者基于队列实现。这里使用PriorityQueue实现。
class Solution {
public:
priority_queue<int,vector<int>,greater<int>> minHeap;
priority_queue<int,vector<int>,less<int>> maxHeap;
int count=0;
void Insert(int num) { //个数为偶数的话,则先插入到大顶堆,并调整,然后将大顶堆中最大的数插入小顶堆中
if(count%2==0){
maxHeap.push(num);
int max = maxHeap.top();
maxHeap.pop();
minHeap.push(max);
}else{//个数为奇数的话,则先插入到小顶堆,然后将小顶堆中最小的数插入大顶堆中
minHeap.push(num);
int min = minHeap.top();
minHeap.pop();
maxHeap.push(min);
}
count++;
}
double GetMedian() {
if(count%2==0){//当前为偶数个,则取小顶堆和大顶堆的堆顶元素求平均
return (minHeap.top()+maxHeap.top())/2.0;
}else return minHeap.top();
}
};
时间复杂度:Insert()为O(logn), GetMedian()为O(1)
空间复杂度:O(n)