LeetCode 295:数据流的中位数(Median Finder)------ Java 题解 ✅
🔗 题目链接
👉 https://leetcode.cn/problems/find-median-from-data-stream/
📖 内容概要
设计一个数据结构,支持:
- 动态添加整数
- 实时返回当前已添加数据的中位数
✅ 使用 双堆(大小顶堆)
✅ 插入 O(log n)
✅ 查询 O(1)
✅ 面试 & 系统设计高频题
💡 解题思路(核心)
一、核心思想:以空间换时间
始终维持左右两部分数据:
- 左半部分 ≤ 中位数
- 右半部分 ≥ 中位数
二、数据结构设计
| 堆 | 类型 | 作用 |
|---|---|---|
left |
大顶堆 | 保存较小的一半 |
right |
小顶堆 | 保存较大的一半 |
left (maxHeap) | right (minHeap)
≤ median | ≥ median
三、堆的平衡规则(非常关键)
✅ 始终保持:
left.size() == right.size()
或
left.size() == right.size() + 1
🔄 addNum 流程详解
情况 1:当前两堆大小相等
left.size() == right.size()
这时要保证left的大小比right大1,我们肯定要往left中加一个元素;但同时要保证中位数位置的正确性,我们不知道待添加元素和right的堆顶元素谁更小,所以先将待添加元素放到right中,相当于在right中做了一个排序,再把right的最小值(堆顶)弹出放到left中。
策略:
- 新数先入
right - 把
right的最小值移到left
✅ 保证 left 永远多一个
java
right.offer(num);
left.offer(right.poll());
情况 2:当前 left 多一个元素
left.size() > right.size()
这时要保证left的大小和right大相等,我们要往right中加一个元素;但同时要保证中位数位置的正确性,我们不知道待添加元素和left的堆顶元素谁更大,所以先将待添加元素放到left,相当于在left中做了一个排序,再把left的最大值(堆顶)弹出放到right中。
策略:
- 新数先入
left - 把
left的最大值移到right
java
left.offer(num);
right.offer(left.poll());
🎯 findMedian 规则
| 情况 | 中位数 |
|---|---|
| 奇数个元素 | left.peek() |
| 偶数个元素 | (left + right) / 2.0 |
✅ 举例说明
依次添加:1, 2, 3, 4
| 操作 | left (大顶堆) | right (小顶堆) | median |
|---|---|---|---|
| add 1 | 1 | \[\] | 1 |
| add 2 | 1 | 2 | 1.5 |
| add 3 | 2,1 | 3 | 2 |
| add 4 | 2,1 | 3,4 | 2.5 |
✅ AC 代码(Java)
java
class MedianFinder {
private PriorityQueue<Integer> left; // 大顶堆
private PriorityQueue<Integer> right; // 小顶堆
public MedianFinder() {
left = new PriorityQueue<>((a, b) -> b - a);
right = new PriorityQueue<>();
}
public void addNum(int num) {
if (left.size() == right.size()) {
right.offer(num);
left.offer(right.poll());
} else {
left.offer(num);
right.offer(left.poll());
}
}
public double findMedian() {
if (left.size() > right.size()) {
return left.peek();
}
return (left.peek() + right.peek()) / 2.0;
}
}
⏱️ 复杂度分析
| 操作 | 复杂度 |
|---|---|
| addNum | O(log n) |
| findMedian | O(1) |
| 空间复杂度 | O(n) |
✅ 总结一句话
用大顶堆存小的一半,用小顶堆存大的一半,始终保持左堆 ≥ 右堆,中位数就在堆顶。
📌 面试加分点(建议记住)
- ✅ 为什么先插右堆再移左堆?
👉 保证left永远是"较小的一半的最大值" - ✅ 为什么不用排序?
👉 排序 O(n log n),双堆 O(log n) - ✅ 可扩展到:
- 滑动窗口中位数
- Top K 动态数据