剑指 Offer 41. 数据流中的中位数
思路
方法一:简单排序
将数字存储在可调整大小的容器中。每次需要输出中间值时,对容器进行排序并输出中间值。
复杂度分析
时间复杂度:O(nlogn)+O(1)≃O(nlogn)。
添加一个数字对于一个有效调整大小方案的容器来说需要花费 O(1) 的时间。
找到中间值主要取决于发生的排序。对于标准比较排序,这需要 O(nlogn) 时间。
空间复杂度:O(n) 线性空间,用于在容器中保存输入。除了需要的空间之外没有其他空间(因为通常可以在适当的位置进行排序)。
方法二:插入排序
保持输入容器始终排序。
复杂度分析
时间复杂度:O(n)+O(logn)≈O(n).
二分搜索需要花费 O(logn) 时间才能找到正确的插入位置。
插入可能需要花费 O(n) 的时间,因为必须在容器中移动元素为新元素腾出空间。
空间复杂度:O(n) 线性空间,用于在容器中保存输入。
方法三:一个大顶堆 + 一个小顶堆
一个大顶堆maxHeap, 一个小顶堆minHeap,大顶堆保存前一半的元素,小顶堆保存后一半的元素。
与此同时,始终保持两个堆的大小之差不超过1。
为了保证这两个堆大小之差不超过1,对于元素num的插入过程需分为以下3种情况:
(1) 大顶堆的元素个数 = 小顶堆的元素个数
两个堆大小相等,如果小顶堆为空或者待插入的元素num≥小顶堆的堆顶元素,则直接把num插入小顶堆中;
否则,把num插入大顶堆中。
(2) 大顶堆的元素个数 < 小顶堆的元素个数
如果待插入的元素num≤小顶堆的堆顶元素,则直接把num插入大顶堆中;
否则,弹出小顶堆的堆顶元素,并将其插入大顶堆,之后把num插入小顶堆。
(3) 大顶堆的元素个数 > 小顶堆的元素个数
如果待插入的元素num≥大顶堆的堆顶元素,则直接把num插入小顶堆中;
否则,弹出大顶堆的堆顶元素,并将其插入小顶堆,之后把num插入大顶堆。
复杂度分析
时间复杂度:O(logn)。堆的插入删除操作都是O(logn)。
空间复杂度:O(n) 。小顶堆 和大顶堆一共同时保存 n 个元素。
1 class MedianFinder { 2 private: 3 priority_queue<int, vector<int>, greater<int>> minHeap; 4 priority_queue<int, vector<int>, less<int>> maxHeap; 5 public: 6 /** initialize your data structure here. */ 7 MedianFinder() { 8 9 } 10 11 /*始终保持两个堆的大小之差不超过1*/ 12 void addNum(int num) { 13 if(maxHeap.size() == minHeap.size()) { 14 /*如果两个堆大小相等*/ 15 if(minHeap.empty()) { 16 minHeap.push(num); 17 return; 18 } 19 20 if(num >= minHeap.top()) { 21 minHeap.push(num); 22 } else { 23 maxHeap.push(num); 24 } 25 26 } else if(maxHeap.size() < minHeap.size()) { 27 /*如果大顶堆的元素少于小顶堆*/ 28 if(num <= minHeap.top()) { 29 maxHeap.push(num); 30 } else { 31 maxHeap.push(minHeap.top()); 32 minHeap.pop(); 33 minHeap.push(num); 34 } 35 36 } else { 37 /*如果大顶堆的元素多于小顶堆*/ 38 if(num >= maxHeap.top()) { 39 minHeap.push(num); 40 } else { 41 minHeap.push(maxHeap.top()); 42 maxHeap.pop(); 43 maxHeap.push(num); 44 } 45 } 46 } 47 48 double findMedian() { 49 if(maxHeap.size() == minHeap.size()) { 50 return (maxHeap.top() + minHeap.top()) / 2.0; 51 } else if(maxHeap.size() < minHeap.size()) { 52 return minHeap.top(); 53 } else { 54 return maxHeap.top(); 55 } 56 } 57 }; 58 59 /** 60 * Your MedianFinder object will be instantiated and called as such: 61 * MedianFinder* obj = new MedianFinder(); 62 * obj->addNum(num); 63 * double param_2 = obj->findMedian(); 64 */
参考