BM48 数据流中的中位数
描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
数据范围:数据流中数个数满足 ,大小满足
进阶: 空间复杂度 , 时间复杂度
示例1
输入:
[5,2,3,4,1,6,7,0,8]
复制返回值:
"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
复制说明:
数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...
示例2
输入:
[1,1,1]
复制返回值:
"1.00 1.00 1.00 "
题解
思路:
使用2优先级队列little_queue和big_queue,让前者以大根堆的形式存放最小的一半数据,后者以小根堆的形式存放另一半数据。这样前者的top元素就是数组中一半元素中最大的,后者的top元素就是数组中后一半元素最小的。然后我们可以根据奇偶性来判断如何返回中位数。
注意:
- 1. 在getmedian函数中,实际上可以不用判断奇偶性,只需要判断Little_queue.size是否大于big_queue.size
- 2. 在根据元素个数完成插入后可能需要进行一次堆顶元素的交换,目的是为了保持little_queue的堆顶元素是最小一半数据的最大值,big_queue的堆顶元素是最大一半数据的最小值
代码如下:
class Solution
{
public:
void Insert(int num)
{
if (little_queue.empty())
{
little_queue.push(num);// 第一个元素我们放到大根堆中,因为 1 % 2= 1,刚好返回大根堆的top元素
}
else
{
if (little_queue.size() == big_queue.size())// 如果两边的元素个数相等,则放入大根堆中
{
little_queue.push(num);
}
else if (little_queue.size() > big_queue.size())// 否则放入小根堆中,这个逻辑不会存在little_queue元素个数小于big_queue的情况
{
big_queue.push(num);
}
int a = little_queue.top();
int b = big_queue.top();
if (a > b)// 完成插入后,小根堆的top元素是那一半的最小值,大根堆的top是其最大值。
{ // 如果 little_queue.top > big_queue.top,说明需要交换大根堆和小根堆的堆顶元素
// 以便保持他们的2个元素分别是已插入元素中最小一半中的最大值,和最大一半元素中的最小值
little_queue.pop();
big_queue.pop();
little_queue.push(b);
big_queue.push(a);
}
}
}
double GetMedian()
{
int total_count = little_queue.size() + big_queue.size();
if (total_count % 2 == 1)
{
return static_cast<double>(little_queue.top());
}
return (static_cast<double>(little_queue.top()) + static_cast<double>(big_queue.top())) / 2;
}
private:
// 大根堆,用于存放最小的一半元素
std::priority_queue<int, std::vector<int>, std::less<int>> little_queue;
// 小根堆,用于存放最大的一半元素
std::priority_queue<int, std::vector<int>, std::greater<int>> big_queue;
};
牛客网官方题解
思路:
除了插入排序,我们换种思路,因为插入排序每次要遍历整个已经有的数组,很浪费时间,有没有什么可以找到插入位置时能够更方便。
我们来看看中位数的特征,它是数组中间个数字或者两个数字的均值,它是数组较小的一半元素中最大的一个,同时也是数组较大的一半元素中最小的一个。那我们只要每次维护最小的一半元素和最大的一半元素,并能快速得到它们的最大值和最小值,那不就可以了嘛。这时候就可以想到了堆排序的优先队列。
具体做法:
- step 1:我们可以维护两个堆,分别是大顶堆min,用于存储较小的值,其中顶部最大;小顶堆max,用于存储较大的值,其中顶部最小,则中位数只会在两个堆的堆顶出现。
- step 2:我们可以约定奇数个元素时取大顶堆的顶部值,偶数个元素时取两堆顶的平均值,则可以发现两个堆的数据长度要么是相等的,要么奇数时大顶堆会多一个。
- step 3:每次输入的数据流先进入大顶堆排序,然后将小顶堆的最大值弹入大顶堆中,完成整个的排序。
- step 4:但是因为大顶堆的数据不可能会比小顶堆少一个,因此需要再比较二者的长度,若是小顶堆长度小于大顶堆,需要从大顶堆中弹出最小值到大顶堆中进行平衡。
class Solution {
public:
//大顶堆,元素数值较小
priority_queue<int> min;
//小顶堆,元素数值都比大顶堆大
priority_queue<int, vector<int>, greater<int>> max;
//维护两个堆,取两个堆顶部即可
void Insert(int num) {
//先加入较小部分
min.push(num);
//将较小部分的最大值取出,送入到较大部分
max.push(min.top());
min.pop();
//平衡两个堆的数量
if(min.size() < max.size()){
min.push(max.top());
max.pop();
}
}
double GetMedian() {
//奇数个
if(min.size() > max.size())
return (double)min.top();
else
//偶数个
return (double)(min.top() + max.top()) / 2;
}
};