数据流中的中位数

题目

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

数据范围:数据流中数个数满足 1≤n≤1000 ,大小满足 1≤val≤1000

进阶: 空间复杂度 O(n) , 时间复杂度 O(nlogn)
示例1

1
2
3
4
5
输入:[5,2,3,4,1,6,7,0,8]

返回值:"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "

说明:数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...

示例2

1
2
3
4
输入:[1,1,1]


返回值:"1.00 1.00 1.00 "

题解

解题关键

思路

方法 1:插入排序法

step 1:用一数组存储输入的数据流。
step 2:Insert函数在插入的同时,遍历之前存储在数组中的数据,按照递增顺序依次插入,如此一来,加入的数据流便是有序的。
step 3:GetMedian函数可以根据下标直接访问中位数,分为数组为奇数个元素和偶数个元素两种情况。记得需要类型转换为double。

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
import java.util.*;
public class Solution {
private ArrayList<Integer> list = new ArrayList<>();
public void Insert(Integer num) {
if(list.isEmpty()){
list.add(num);
}else{
int i = 0;
for(; i < list.size(); i++){
if(num <= list.get(i)){
break;
}
}
list.add(i, num);
}
}

public Double GetMedian() {
int n = list.size();
if(n%2 == 1){
return (double)list.get(n / 2);
}else{
double a = list.get(n / 2);
double b = list.get(n / 2 - 1);
return (a + b) / 2;
}
}

}

方法 2:堆排序

知识点:优先队列

优先队列即PriorityQueue,是一种内置的机遇堆排序的容器,分为大顶堆与小顶堆,大顶堆的堆顶为最大元素,其余更小的元素在堆下方,小顶堆与其刚好相反。且因为容器内部的次序基于堆排序,因此每次插入元素时间复杂度都是O(log2n),而每次取出堆顶元素都是直接取出。

思路:

除了插入排序,我们换种思路,因为插入排序每次要遍历整个已经有的数组,很浪费时间,有没有什么可以找到插入位置时能够更方便。

我们来看看中位数的特征,它是数组中间个数字或者两个数字的均值,它是数组较小的一半元素中最大的一个,同时也是数组较大的一半元素中最小的一个。那我们只要每次维护最小的一半元素和最大的一半元素,并能快速得到它们的最大值和最小值,那不就可以了嘛。这时候就可以想到了堆排序的优先队列。

具体做法:

step 1:我们可以维护两个堆,分别是大顶堆min,用于存储较小的值,其中顶部最大;小顶堆max,用于存储较大的值,其中顶部最小,则中位数只会在两个堆的堆顶出现。
step 2:我们可以约定奇数个元素时取大顶堆的顶部值,偶数个元素时取两堆顶的平均值,则可以发现两个堆的数据长度要么是相等的,要么奇数时大顶堆会多一个。
step 3:每次输入的数据流先进入大顶堆排序,然后将小顶堆的最大值弹入大顶堆中,完成整个的排序。
step 4:但是因为大顶堆的数据不可能会比小顶堆少一个,因此需要再比较二者的长度,若是小顶堆长度小于大顶堆,需要从大顶堆中弹出最小值到大顶堆中进行平衡。

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
import java.util.*;
public class Solution {
//小顶堆,元素数值都比大顶堆大
private PriorityQueue<Integer> max = new PriorityQueue<>();
//大顶堆,元素数值较小
private PriorityQueue<Integer> min = new PriorityQueue<>((o1, o2)->o2.compareTo(o1));
//维护两个堆,取两个堆顶部即与中位数相关
public void Insert(Integer num) {
//先加入较小部分
min.offer(num);
//将较小部分的最大值取出,送入到较大部分
max.offer(min.poll());
//平衡两个堆的数量
if(min.size() < max.size())
min.offer(max.poll());
}

public Double GetMedian() {
//奇数个
if(min.size() > max.size())
return (double)min.peek();
else
//偶数个
return (double)(min.peek() + max.peek()) / 2;
}

}

数据流中的中位数
http://lhystutest.top/2022/11/27/算法/堆/栈/数据流中的中位数/
作者
lhy
发布于
2022年11月27日
许可协议