「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战」
1、题目
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如:
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
- 最多会对 addNum、findMedian 进行 50000 次调用
2、知识补充
cloud.tencent.com/developer/a…
数据结构 - 堆
- Heap是一种数据结构具有以下的特点: (1)完全二叉树; (2)heap中存储的值是偏序;
- Min-heap: 父节点的值小于或等于子节点的值;
- Max-heap: 父节点的值大于或等于子节点的值;
优先队列:
priority_queue称为“优先队列”,其底层是用堆实现。在优先队列中,队首元素一定是当前队列中优先级最高的哪一个。
默认的定义优先队列是大根堆,即父节点的值大于子节点的值。
获取堆顶元素
top():可以获得队首元素(堆顶元素),时间复杂度为O(1)。 与队列不一样的是,优先队列通过top()函数来访问队首元素(堆顶元素)。(队列是通过front()函数和back()函数访问下标)
入队
push(x) :令x入队,时间复杂度为O(logN),其中N为当前优先队列中的元素个数。
出队
pop(): 令队首元素(堆顶元素)出队,时间复杂度为O(logN),其中N为当前优先队列中的元素个数。
检测是否为空
empty():检测优先队列是否为空,返回true为空,false为非空。时间复杂度为O(1)
获取元素个数
size():用来获得优先队列中元素的个数,时间复杂度为 O(1)
案例代码
#include
#include
using namespace std;
int main(){
priority_queue q;
//入队
q.push(3);
q.push(4);
q.push(1);
//通过下标访问元素
printf("%d\n",q.top());//输出4
//出队
q.pop();
printf("%d\n",q.top());//输出3
//检测队列是否为空
if(q.empty() == true) {
printf("Empty\n");
} else {
printf("Not Empty\n");
}
//获取长度
//printf("%d\n",q.size());//输出3
}
大根堆就是根节点是整棵树的最大值,小根堆就是根节点是整棵树的最小值。
基本数据类型的优先级设置
一般情况下,数字大的优先级更高。(char类型的为字典序最大) 对于基本结构的优先级设置。下面两种优先队列的定义是等价的:
priority_queue<int> q; // 大根堆
priority_queue<int,vector<int>,less<int> > q;
如果想让优先队列总是把最小的元素放在队首,需进行以下定义:
priority_queue<int, vector<int>, greater<int>> q //小根堆
3、思路
(双顶堆)
我们可以使用两个堆来解决这个问题,使用一个大顶堆保存整个数据流中较小的那一半元素,再使用一个小顶堆保存整个数据流中较大的那一半元素,同时大顶堆堆顶元素 <= 小顶堆堆顶的元素。
具体过程:
- 1、建立一个大根堆,一个小根堆。大根堆存储小于当前中位数,小根堆存储大于等于当前中位数。
- 2、执行
addNum操作时,如果新加入的元素num小于等于大顶堆down堆顶元素,则加入大根堆中,否则加入小根堆中。 - 3、为了维持左右两边数的数量,我们可以让大根堆的大小最多比小根堆大
1,每次插入后,可能会导致数量不平衡,所以如果插入后哪边的元素过多了,我们将该堆的堆顶元素弹出插入到另一个堆中。 - 4、当数据个数是奇数的时候,中位数就是大根堆堆顶,是偶数的时候就是大根堆与小根堆堆顶的平均值。
4、c++代码
class MedianFinder {
public:
priority_queue<int, vector<int>, greater<int>> up; //小根堆
priority_queue<int> down; // 大根堆
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if (down.empty() || num <= down.top()) {
down.push(num);
if (down.size() > up.size() + 1) {
up.push(down.top());
down.pop();
}
} else {
up.push(num);
if (up.size() > down.size()) {
down.push(up.top());
up.pop();
}
}
}
double findMedian() {
if ((down.size() + up.size()) % 2) return down.top();
return (down.top() + up.top()) / 2.0;
}
};