数据流中的中位数
题目
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路
如果能获取所有的数据之后,可以利用快排的partition 函数找到拍在第 k 个位置的数字。复杂度为 O ( n ) O(n) O(n)
但是是数据流的话,每增加一个,排一次,就成了 O ( n 2 ) O(n^2) O(n2)?
希望每增加一个数字后,可以快速找到当前的中位数。
可以利用两个set 来实现。 一个存放中位数左边的数字,一个存放中位数右边的数字。
multiset默认是使用 less<T>
的比较函数,*begin() 获取得到的是最小值。
因此,两个set的定义如下,使用 std::greater<>
的仿函数。
std::multiset<int, std::less<int>> rightNums; //存储右边的数字,头部为右边最小的数字
std::multiset<int, std::greater<int>> leftNums; //存储左边的数字,头部为左边最大的数字
- 在新增加一个数字时,当前偶数的话,默认放左边,当前奇数,默认放右边。
- 放入左边时,要检查,num是否大于右边的最小值,
- 如果大于,就将右边的最小值移入左边,把新的num放入右边。
- 如果不大于,就直接放入左边
- 放入右边的话,类似。
获取中位数时,判断当前两个set里面的数字个数:
- 奇数时,中位数是left set 顶端。(偶数默认放左边,放完之后总的为奇数)。
- 偶数时,总位数是left和right顶端的平均值。
实现
class Solution {
public:
void Insert(int num)
{
int size = rightNums.size() + leftNums.size();
if(!(size & 0x1))//偶数
{
if(rightNums.size()!=0 && num > (*rightNums.begin()) )
{
int minInRight = *rightNums.begin();
rightNums.erase(rightNums.begin());
rightNums.insert(num);
leftNums.insert(minInRight);
}
else
leftNums.insert(num);
}
else
{//奇数
if(leftNums.size()!=0 && num < (*leftNums.begin()) )
{
int maxInLeft = *leftNums.begin();
leftNums.erase(leftNums.begin());
leftNums.insert(num);
rightNums.insert(maxInLeft);
}
else
rightNums.insert(num);
}
}
double GetMedian()
{
int size = rightNums.size() + leftNums.size();
if(!(size & 0x1))
{//偶数
return ((*leftNums.begin())+(*rightNums.begin()))/2.0;
}
else
{//奇数,放在left里面
return (*leftNums.begin());
}
}
private:
std::multiset<int, std::less<int>> rightNums;
std::multiset<int, std::greater<int>> leftNums;
};