题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
分析
如果数据在容器中已经排好序,那么中位数可以由P1和P2指向的数得到。如果数据个数为奇数个,则P1和P2指向同一个数,这个数也就是要求的中位数,如果数据个数是偶数个,那么所求的数就是P1和P2所指向的数的平均数。
通过观察发现,整个数据容器被分隔成两部分,容器左边的数比右边的数小,而且,P1指向的数据是左边部分最大数,P2指向的数据是右边部分最小数。 这一发现很重要,即使P1左边和P2右边的数据没有排序,我们也可以根据左边最大值和右边最小值来得到中位数。那么如何才能快速得到一个容器中的最大值和最小值呢?这里可以使用大顶堆和小顶堆。
也就是说,用大顶堆实现左边的的数据容器,小顶堆实现右边的数据容器。有一点需要注意,我们要保证数据被平均分配到两个堆中,因此两堆大小之差不能超过1,我这里用的是如果个数之差为1,那么是大顶堆的个数多1个。
考虑以下2种特殊情况:
(1)输入的是1、2、3、4、5、6、7、8(已经从小到大排好序的序列)。 输入1之后,假设分配到了大顶堆,此时1是大顶堆的top;
接着输入2,假设还是分配到了大顶堆,那么2成为了大顶堆的top;此时不满足两堆中数的个数之差小于等于1的要求。那么需要想方法将这个2移到小顶堆中。这里可以用对比size来实现。
依次类推。(2)输入的是8、7、6、5、4、3、2、1(已经从大到小排好序的序列)。 输入8之后,假设分配到了大顶堆,此时8是大顶堆的top;
接着输入7,假设还是分配到了大顶堆,那么8还是大顶堆的top;此时不满足两堆中数的个数之差小于等于1的要求。那么需要想方法将这个8移到小顶堆中。)这里同样可以用对比size来实现。
以此类推。
以上两种情况都是最坏情况。
代码
class Solution{
priority_queue<int,vector<int>,less<int>> max;
priority_queue<int,vector<int>,greater<int>> min;
public:
void Insert(int num){
if(max.empty()||num<=max.top())
max.push(num);
else
min.push(num);
if(max.size()==min.size()-1){
max.push(min.top());
min.pop();
}
if(max.size()==min.size()+2){
min.push(max.top());
max.pop();
}
}
double GetMedian(){
return max.size()==min.size()?(max.top()+min.top())/2.0:max.top();
}
};
分析(另)
另一种解法,思路类似,实现方法是数据流中奇数位的数插入小顶堆中,偶数位的数插入大顶堆中。同时还要保证大顶堆中的所有数都要小于小顶堆中的数。
考虑一种特殊情况,例如,插入偶数位的数,按照分配规则应该插入大顶堆中,但是这个数大于小顶堆的最小值怎么办?如果直接插入大顶堆,就不符合大顶堆所有值都小于小顶堆的值这一条件。可以这样解决:想把该数插入小顶堆,然后进行堆排序,接着讲小顶堆的最小值拿出来插入到大顶堆中,这样就满足所有条件了。同理插入奇数位的数时也要考虑这种特殊情况。
代码
class Solution {
public:
//插入原则:奇数位的数插入小顶堆,偶数位数的插入大顶堆,除特殊情况外
void Insert(int num)
{
/*特殊情况1:当插入奇数位的数num时,应插入小顶堆里,但是当num小于大顶堆的最大值时,
应将其插入大顶堆,然后将大顶堆的最大值插入小顶堆中*/
if (((min.size() + max.size()) & 1) == 0)
{
if (max.size() > 0 && num < max[0])
{
max.push_back(num);
push_heap(max.begin(), max.end());
num = max[0];
pop_heap(max.begin(), max.end());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(), min.end(),greater<int>());
}
else
{
/*特殊情况2:当插入偶数位的数num时,应插入大顶堆里,
但是当num大于小顶堆的最小值时,应将其插入小顶堆,然后将小顶堆的最小值插入大顶堆中*/
if (min.size() > 0 && num > min[0])
{
min.push_back(num);
push_heap(min.begin(), min.end(),greater<int>());
num = min[0];
pop_heap(min.begin(), min.end(),greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(), max.end());
}
}
double GetMedian()
{
int size = min.size() + max.size();
if (size == 0)
return 0;
if ((size & 1) == 1)
return min[0];
else
return (double)(max[0] + min[0]) / 2;
}
private:
vector<int> min;
vector<int> max;
};