剑指offer--41-数据流中的中位数

剑指offer–41-数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。
1
2
3
4
5
6
7
8
9
10
11
12
13
示例 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]

思路 : 两个堆

待优化的思路1:快排

  • 其中位数的计算方法:首先对数组执行排序(使用O(NlogN) 时间),然后返回中间元素即可(使用 O(1)时间)。
  • 针对本题,根据以上思路,可以将数据流保存在一个列表中,并在添加元素时 保持数组有序 。此方法的时间复杂度为 O(N),其中包括: 查找元素插入位置O(logN) (二分查找)、向数组某位置插入元素 O(N) (插入位置之后的元素都需要向后移动一位)。

思路2: 两个堆

  • 事实上,我们只关心在中间的那两个数(或者一个数),其它数没有必要进行 “比较” 和 “交换” 的操作。
  • 在我们学习过的数据结构里,堆就有类似的性质,每次都从堆里得到一个 “最值” 而其它元素无需排序,这样就可以以O(logN) 的复杂度每次都从堆中取出最值。

mark

mark

mark

mark

总结:

  • 为了找到添加新数据以后,数据流的中位数,我们让这个新数据在大顶堆和小顶堆中都走了一遍。而为了让大顶堆的元素多 1 个,我们让从小顶堆中又拿出一个元素“送回”给大顶堆;
  • 将元素放入优先队列以后,优先队列会以对数时间复杂度自行调整,把“最优值”放入堆顶,这是使用优先队列解决这个问题的原因。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.PriorityQueue;

public class MedianFinder {

/**
* 当前大顶堆和小顶堆的元素个数之和
*/
private int count;
private PriorityQueue<Integer> maxheap;
private PriorityQueue<Integer> minheap;

/**
* initialize your data structure here.
*/
public MedianFinder() {
count = 0;
maxheap = new PriorityQueue<>((x, y) -> y - x);
minheap = new PriorityQueue<>();
}

public void addNum(int num) {
count += 1;
maxheap.offer(num);
minheap.add(maxheap.poll());
// 如果两个堆合起来的元素个数是奇数,小顶堆要拿出堆顶元素给大顶堆
if ((count & 1) != 0) {
maxheap.add(minheap.poll());
}
}

public double findMedian() {
if ((count & 1) == 0) {
// 如果两个堆合起来的元素个数是偶数,数据流的中位数就是各自堆顶元素的平均值
return (double) (maxheap.peek() + minheap.peek()) / 2;
} else {
// 如果两个堆合起来的元素个数是奇数,数据流的中位数大顶堆的堆顶元素
return (double) maxheap.peek();
}
}
}

复杂度分析:

  • 时间复杂度:O(logN) 优先队列的出队入队操作都是对数级别的,数据在两个堆中间来回操作是常数级别的,综上时间复杂度是 O(logN) 级别的。
  • 空间复杂度:O(N) 占用堆的大小
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信