剑指 Offer 41. 数据流中的中位数(295. 数据流的中位数)
题目:
思路:
【1】大小顶堆的方式(优先队列):
【2】有序集合 + 双指针(差不多就是采用双指针加有序集合模拟两个堆的感觉)
代码展示:
有序集合 + 双指针的方式:
class MedianFinder { // 第一个Integer存放数值 第二个Integer存放该数值的个数 TreeMap<Integer, Integer> nums; int n;// 记录数据个数 用于判断奇偶 int[] left; int[] right; public MedianFinder() { nums = new TreeMap<Integer, Integer>(); n = 0; // 由于这里的有序集合是通过TreeMap(基于红黑树)实现的 // 所以TreeMap中的数据不存在空间上的相临关系 // 那么我们就需要对指针进行一些改造(increase和decrease方法) // 指针[0]处存放指针指向的数据的值 // [1]处存放 由指针记录的该数值的个数 后面简称计数索引 left = new int[2]; right = new int[2]; } public void addNum(int num) { // 若不为重复数字 则num的映射为1 // 若为重复数字 num的映射为num+1 nums.put(num, nums.getOrDefault(num, 0) + 1); // n为数据个数 if (n == 0) { left[0] = right[0] = num; //索引记录下该数值个数为1 left[1] = right[1] = 1; } else if ((n & 1) != 0) {//奇数 123 //0123 if (num < left[0]) { decrease(left); //1234 } else { increase(right); } } else {//偶数 1 23 4 //12334 if (left[0] < num && num < right[0]) { increase(left); decrease(right); //12345 } else if (num >= right[0]) { increase(left); //01234 } else {// num <= left[0]情况 decrease(right); //如果num恰等于left指向的值,那么num将被插入到left右侧,使得left和right间距增大 //所以我们还需要额外让left指向移动后的right //不能直接increase(left) 因为increase(left)只适用于如果num=left[0]的情况 若num<left[0] 只需左移right,left不变 此时若再左移left则会出错 System.arraycopy(right, 0, left, 0, 2); } } n++; } public double findMedian() { return (left[0] + right[0]) / 2.0; } /** * 此函数的关键是:是否要将指针[0]处重新赋值 * 相当于先假设:指针移动之后[0]处所存放的数与移动之前是相同的,那么计数索引++ * 如果是不同的,那么计数索引应该大于Map中记录的数值对应的个数 则需要将指针[0]处重新赋值 并将计数索引重置为1 * 如果是相同的,那么Map中记录的数值对应的个数应该与计数索引++后相等 则无需将指针[0]处重新赋值 * @param iterator */ private void increase(int[] iterator) { iterator[1]++;//计数索引++ // get方法获取iterator[0]存放的值 也就是索引++之前对应的数值 // 若计数索引>数值的映射值(即Map中记录的数值对应的个数) // 说明该数字为唯一数字 if (iterator[1] > nums.get(iterator[0])) { // 则从一群比原数值大的值中选出最小值 将该最小值赋给iterator[0] // ceilingKey() 返回大于或等于给定键的最小键 iterator[0] = nums.ceilingKey(iterator[0] + 1); // 将计数索引值重置为1 方便进行下一次指针操作 iterator[1] = 1; } // 若计数索引<=数值的映射值(说明该数字为重复数字) // 则只进行前面的计数索引++ 不需要对指针[0]处重新赋值 } /** * 此函数的关键是:是否要将指针[0]处重新赋值 * 相当于先假设:指针移动之后[0]处所存放的数与移动之前是不同的,那么计数索引-- * 如果是不同的,那么计数索引--后==0 则需要将指针[0]处重新赋值 并将计数索引设置为移动后的数值对应的个数 * 如果是相同的,那么计数索引--后!=0 则无需将指针[0]处重新赋值 * @param iterator */ private void decrease(int[] iterator) { iterator[1]--; // 若计数索引--之后==0(说明该数字为唯一数字) if (iterator[1] == 0) { // 则从一群比原数值小的值中选出最大值 将该最大值赋给iterator[0] // floorKey() 返回小于或等于给定键的最大键 iterator[0] = nums.floorKey(iterator[0] - 1); // 将该数值的个数赋值给计数索引 iterator[1] = nums.get(iterator[0]); } // 若计数索引--之后!=0(说明该数字为重复数字) // 则只进行前面的计数索引-- 不需要对指针[0]处重新赋值 } }
大小顶堆的方式:
//时间117 ms击败27.30% //内存70.3 MB击败6.27% //时间复杂度:查找中位数 O(1) : 获取堆顶元素使用 O(1) 时间; //添加数字 O(logN) : 堆的插入和弹出操作使用 O(logN) 时间。 //空间复杂度 O(N) : 其中 N 为数据流中的元素数量,小顶堆 A 和大顶堆 B 最多同时保存 N 个元素。 class MedianFinder { Queue<Integer> A, B; public MedianFinder() { A = new PriorityQueue<>(); // 小顶堆,保存较大的一半 B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半 } public void addNum(int num) { if(A.size() != B.size()) { A.add(num); B.add(A.poll()); } else { B.add(num); A.add(B.poll()); } } public double findMedian() { return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0; } } //速度更快的写法,思路是一样的,写法也差不多,当时下面貌似更快 //时间98 ms击败81.53% //内存70.2 MB击败9.96% class MedianFinder { PriorityQueue<Integer> left = new PriorityQueue<>((a, b) -> b - a); PriorityQueue<Integer> right = new PriorityQueue<>(); public MedianFinder() {} public void addNum(int num) { if (left.size() == right.size()) { if (left.isEmpty() || num <= right.peek()) { left.add(num); } else { right.add(num); left.add(right.poll()); } } else { if (num >= left.peek()) { right.add(num); } else { left.add(num); right.add(left.poll()); } } } public double findMedian() { if (left.size() != right.size()) { return left.peek(); } else { return (left.peek() + right.peek()) / 2.0; } } } /** * Your MedianFinder object will be instantiated and called as such: * MedianFinder obj = new MedianFinder(); * obj.addNum(num); * double param_2 = obj.findMedian(); */
分类:
leetcode题目
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构