LeetCode 295:数据流的中位数(Median Finder)—— Java 题解 ✅

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的大小比right1,我们肯定要往left中加一个元素;但同时要保证中位数位置的正确性,我们不知道待添加元素和right的堆顶元素谁更小,所以先将待添加元素放到right中,相当于在right中做了一个排序,再把right的最小值(堆顶)弹出放到left中。

策略:

  1. 新数先入 right
  2. 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中。

策略:

  1. 新数先入 left
  2. 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 动态数据
相关推荐
小bo波2 小时前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
SamDeepThinking2 小时前
高并发场景下,CompletableFuture与ForkJoinPool该如何取舍?
java·后端·面试
用户938515635073 小时前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
To_OC4 小时前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法
饼干哥哥5 小时前
Reddit VOC调研太慢?搭一个AI专家团队半小时洞察任何品类|以猫用饮水机为例
人工智能·算法·ai编程
张不才5 小时前
CPU 100% 了怎么办?Java 性能排障的标准化操作
java·后端
地平线开发者6 小时前
Transformer模型部署之性能优化指南
算法
地平线开发者6 小时前
人在途中:从“编译失败”到“模型可落地”——CUDA 自定义算子
算法·自动驾驶
shepherd1116 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
半个落月9 小时前
从递归到快速排序:用 JavaScript 把分治思想讲明白
javascript·算法·面试