题目: 设计一个支持以下两种操作的数据结构:
```go
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
原始题目
满足两个特性:
1.大顶堆中最大的数值(堆顶)<=小顶堆中的最小数(堆顶),
所以大堆堆顶和小堆堆顶中可以存储中位数
2.两个堆中元素相差为0,或者为1,不能>1
class MedianFinder {
Queue<Integer> minHeap, maxHeap;
public MedianFinder() {
minHeap = new PriorityQueue<>();
maxHeap = new PriorityQueue<>((a, b)->(b - a));
}
public void addNum(int num) {
// 优先插入minHeap
if (minHeap.size() == 0 || num > minHeap.peek()) {
minHeap.add(num);
} else {
maxHeap.add(num);
}
if (minHeap.size() > maxHeap.size() + 1)
maxHeap.add(minHeap.poll());
if (maxHeap.size() > minHeap.size() + 1)
minHeap.add(maxHeap.poll());
}
public double findMedian() {
if (minHeap.size() == maxHeap.size())
return 1.0 * (minHeap.peek() + maxHeap.peek()) / 2;
return minHeap.size() > maxHeap.size() ? minHeap.peek() : maxHeap.peek();
}
}
优化后的判断插入:利用堆的特性自己调整
public void addNum(int num) { // minHeap是优先的
// 把num加入min堆,利用堆自己调整大小
minHeap.add(num);
// 此时把min堆顶最小值(不一定是上面num) 给max堆,
// 平衡两个堆长度,防止min一直增加
maxHeap.add(minHeap.poll());
// 在这里真正把元素添加到min堆
// 如果两个堆元素相等,不用管
// 如果在上一次调用addNum后,两个堆大小一样,
// 那么在上面maxHead.add 之后 max比min长
// 重新 把max堆顶给min堆, 保证min堆比max多一个,或者相等
if (minHeap.size() < maxHeap.size())
minHeap.add(maxHeap.poll());
}
public double findMedian() {
if (minHeap.size() == maxHeap.size())
return 1.0 * (minHeap.peek() + maxHeap.peek()) / 2 ;
else //如果两个堆长度不等, minHeap长度比maxHeap长度大1
return minHeap.peek();
}
进阶一:数据流中所有整数都在 0 到 100 范围内
用一个长度101数组做hash保存每个数字出现的次数。
然后从0开始累加数组和sum,代表前sum个数,显然中位数 就好找了。
public class MedianFinder {
int[] count; // hash表
int total; // 输入数据个数
public MedianFinder() {
count = new int[101];
total = 0;
}
public void addNum(int num) { // 添加一个数据
count[num]++;
total++;
}
public double findMedian() { // 找中位数
if (0 == total % 2) { // total是偶数 找两个中间的数 和除以2
return (findKthNumber(total / 2) + findKthNumber(total / 2 + 1)) / 2.0;
} else { // total是奇数 直接返回中间的数 (从1开始数数)
return findKthNumber(total / 2 + 1);
}
}
private int findKthNumber(int k) { // k从1开始数数
int index = 0;
for (int i = 0; i < 101; i++) {
index += count[i];
// 加上i的个数之后,前面数字个数和>=k,i就是第k个数
if (index >= k) {
return i;
}
}
return -1;
}
}
时间和空间复杂度均是O(1) 因为对长度是101的数组来说,遍历就是O(1) 长度大小101也就是常数级别。
进阶二:数据流中 99% 的整数都在 0 到 100 范围内
和进阶一同样的思路,只不过将大于100的数除去即可,因为有99%的整数都在0到100范围内,所以中位数一定是0到100范围内的某个数,只需根据大于100的数的个数在进阶一的基础上(统计>100的个数)调整即可。