295. 数据流的中位数
欢迎大家参加每日一题系列并提供其他版本,在你也可以按照相同格式提供你的最新的每日一题题解。
每日一题系列
题目描述:
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
进阶:
-
如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
-
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?
解答:
C++
方法一:暴力
暴力接法:直接尾部插入,sort快速排序,取中位数
class MedianFinder {
public:
/** initialize your data structure here. */
vector<int> mem_num;
MedianFinder() {
}
void addNum(int num) {
this->mem_num.push_back(num);//插入
sort(this->mem_num.begin(),this->mem_num.end());//快排
}
double findMedian() {
int len=this->mem_num.size();
//cout<<len<<endl;
double mid_num=0;
if(len%2==0){
mid_num=(double(this->mem_num[len/2])+double(this->mem_num[len/2-1]))/2;
}else if(len%2!=0){
mid_num=double(this->mem_num[len/2]);
}
return mid_num;//中位数
}
};
优化:二分插入,取中位数
class MedianFinder {
public:
/** initialize your data structure here. */
vector<int> mem_num;
MedianFinder() {
}
void addNum(int num) {
auto addr=upper_bound(mem_num.begin(),mem_num.end(),num);
mem_num.insert(addr,num);
}
double findMedian() {
int len=this->mem_num.size();
//cout<<len<<endl;
double mid_num=0;
if(len%2==0){
mid_num=(double(this->mem_num[len/2])+double(this->mem_num[len/2-1]))/2;
}else if(len%2!=0){
mid_num=double(this->mem_num[len/2]);
}
return mid_num;
}
};
方法二:优先队列
将数据分为两个优先队列(MIN与MAX)分别记录大于中位数的数和小于等于中位数的数,优先队列中的每个元素都有优先级,而优先级高(或者低)的将会先出队,而优先级相同的则按照其在优先队列中的顺序依次出队。
优先队列往往使用堆来实现
思路:First_num优先分配给MIN优先队列,New_num大于MIN.top()分配给MAX,若MAX.size()>MIN.size()把MAX.top()给MIN使两个优先队列的队头处于中位数位置;New_num小于等于MIN.top()分配给MIN,若MAX.size()<MIN.size()+1把MIN.top()给MAX使优使两个优先队列的队头处于中位数位置;返回队头即中位数
std::priority_queue默认由大根堆实现,即最大的数在堆顶,看下图:
class MedianFinder {
public:
priority_queue<int> MIN;
priority_queue<int, vector<int>,greater<int>> MAX;
MedianFinder() {}
void addNum(int num) {
if (MIN.empty() || num <= MIN.top()) {
MIN.push(num);
if (MAX.size() + 1 < MIN.size()) {
MAX.push(MIN.top());
MIN.pop();
}
} else {
MAX.push(num);
if (MAX.size() > MIN.size()) {
MIN.push(MAX.top());
MAX.pop();
}
}
}
double findMedian() {
if (MIN.size() > MAX.size()) {
return MIN.top();
}
return (MIN.top() + MAX.top()) / 2.0;
}
};
方法三:有序集+双指针
使用multiset有序集,并维护两个指针分别指向中位数的左右两个数,下程序指针维护未看懂,可以观察实例理解;
class MedianFinder {
multiset<int> nums;//创建有序集
multiset<int>::iterator left, right;//创建中位数的双指针
public:
MedianFinder() : left(nums.end()), right(nums.end()) {}
void addNum(int num) {
int n = nums.size();//n为未插入大小
nums.insert(num);//插入数据
if (!n) {//有序集为空都指向头部
left = right = nums.begin();
} else if (n & 1) {//判断n为奇偶数,n奇数
if (num < *left) {//此时,left与right指向同一个数,此时size()为偶数,新数小于左指针指向的数,所以left——;
left--;
} else {//此时,left与right指向同一个数,此时size()为偶数,新数大于左指针指向的数,所以right++;
right++;
}
} else {//n为偶数
if (num > *left && num < *right) {//此时,left与right指向左右两个数,此时size()为奇数,新数大于左指针指向的数,且小于右指针指向的数所以left++,right--,左右指向同一处;
left++;
right--;
} else if (num >= *right) {//此时,left与right指向左右两个数,此时size()为奇数,新数大于右指针指向的数所以left++,左右指向同一处;
left++;
} else {//此时,left与right指向左右两个数,此时size()为奇数,新数小于左指针指向的数所以 right--,左右指向同一处;
right--;
left = right;
}
}
}
double findMedian() {
return (*left + *right) / 2.0;
}
};
欢迎大家参加每日一题系列并提供其他版本,在你也可以按照相同格式提供你的最新的每日一题题解。
每日一题系列