题目:设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
方法一:利用二分查找元素的插入位置,使所有元素升序排序。
利用lower_bound(),在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。
- 时间复杂度:O(n)。O(logn)+O(n)≈O(n)。
- 空间复杂度:O(n)。使用了数组保存输入。
class MedianFinder {
vector<int> store; // resize-able container
public:
void addNum(int num)
{
if (store.empty())
store.push_back(num);
else
store.insert(lower_bound(store.begin(), store.end(), num), num);
}
double findMedian()
{
int n = store.size();
return n & 1 ? store[n / 2] : (store[n / 2 - 1] + store[n / 2]) * 0.5;
}
};
方法二:优先队列排序(大顶堆,小顶堆)
将中位数左边的数保存在大顶堆中,右边的数保存在小顶堆中。这样我们可以在O(1) 时间内得到中位数。
- 时间复杂度O(logn)。堆插入和删除需要O(logn),查找中位数需要 O(1)。
- 空间复杂度:O(n)。
class MedianFinder {
priority_queue<int> lo; // 大顶堆
priority_queue<int, vector<int>, greater<int>> hi; // 小顶堆
public:
// Adds a number into the data structure.
void addNum(int num)
{
lo.push(num); // 加到大顶堆
hi.push(lo.top()); // 平衡
lo.pop();
if (lo.size() < hi.size()) { // 维护两个堆元素个数
lo.push(hi.top());
hi.pop();
}
}
double findMedian()
{
return lo.size() > hi.size() ? (double) lo.top() : (lo.top() + hi.top()) * 0.5;
}
};
补充priority_queue知识点:
优先队列分两种:
- 最大优先队列,无论入队顺序,当前最大的元素优先出队。
- 最小优先队列,无论入队顺序,当前最小的元素优先出队。
事实上,优先队列的本质上是一个堆,它是一棵完全二叉树,分为小顶堆和大顶堆:
小顶堆是每一个根节点小于左右子节点的完全二叉树,堆顶元素最小,对应最小优先队列;
大顶堆是每一个根节点大于左右子节点的完全二叉树,堆顶元素最大,对应最大优先队列
优先队列函数声明如下:
priority_queue< type, container, function >
type:数据类型;container:实现优先队列的底层容器;function:元素之间的比较方式。
在STL中,默认情况下(不加后面两个参数)是以vector为容器,以 operator< 为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素。
priority_queue<int,vector<int>,greater<int> > small_heap; //构造小顶堆