leetcode每日一题系列-数据流的中位数-「大小根堆」

711 阅读3分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

leetcode-295-数据流的中位数

[博客链接]

菜🐔的学习之路

掘金首页

[题目描述]

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

 void addNum(int num) - 从数据流中添加一个整数到数据结构中。 
 double findMedian() - 返回目前所有元素的中位数。 

示例:

 addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3) 
findMedian() -> 2 

进阶:

  • 如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
  • 如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

Related Topics

  • 设计
  • 双指针
  • 数据流
  • 排序
  • 堆(优先队列)
  • 👍 494 👎 0

[题目链接]

leetcode题目链接

[github地址]

代码链接

[思路介绍]

思路一:大小根堆

  • 首先要明确中位数的定义只和位置有关
  • 也就是说只取决于中间元素(中间两个元素的平均值)
  • 这样我们就很容易想到通过定义两个相同大小相差一个元素数量的数据结构
  • 这样的数据结构需要满足能够按照大小顺序输出最大或最小元素
  • 这样前半部分的最大元素,后半部分的最小元素
    • 若是奇数,则取多一个元素的最小元素即可
    • 若是偶数,则取两个数据结构的平均数
  • 这样的数据结构很容易想到堆
  • 然后每次添加元素更新中位数,保证取中位数的时间复杂度为O(1)
  • 我们定义一个大根堆,一个小根堆,小根堆存入较大元素,大根堆存入较小元素
  • 保证小根堆元素数量>=大根堆元素数量
  • 这样即可求出偶数数量的中位数
  • 分几种情况考虑
    • 第一种:偶数情况,这样再加入元素就变成了奇数
    • 我们需要对比加入元素与大根堆的堆顶元素对比
      • 若大于大根堆堆顶元素,则可以直接加入小根堆中,中位数取小根堆堆顶元素
      • 反之,则取出大根堆堆顶元素,加入小根堆,将加入元素放入大根堆,中位数依旧取小根堆堆顶元素
    • 第二种:技术情况,这样再加入元素就变成了偶数个元素
    • 与上述情况基本思路相同
      • 若大于大根堆堆顶元素,则可以直接加入小根堆中,中位数取两个堆堆顶元素的平均值
      • 反之,则取出大根堆堆顶元素,加入小根堆,将加入元素放入大根堆,中位数取两个堆堆顶元素的平均值
PriorityQueue<Integer> minQ;
        PriorityQueue<Integer> maxQ;
        double mid;

        /**
         * initialize your data structure here.
         */
        public MedianFinder() {
            minQ = new PriorityQueue<>(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2 - o1;
                }
            });
            maxQ = new PriorityQueue<>(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1 - o2;
                }
            });
        }

        public void addNum(int num) {
            if (maxQ.isEmpty()) {
                maxQ.add(num);
                mid = num;
                return;
            }
            if (minQ.isEmpty()) {
                int temp = maxQ.poll();
                minQ.add(Math.min(temp, num));
                maxQ.add(Math.max(temp, num));
                mid = ((double)maxQ.peek() +minQ.peek()) / 2;
                return;
            }
            //两边元素相同
            if (minQ.size() == maxQ.size()) {
                if (num < minQ.peek()) {
                    int temp = minQ.poll();
                    minQ.add(num);
                    maxQ.add(temp);
                } else {
                    maxQ.add(num);
                }
                mid = maxQ.peek();
            }
            //两边元素不同
            else {
                if (num < maxQ.peek()) {
                    minQ.add(num);
                } else {
                    int temp = maxQ.poll();
                    maxQ.add(num);
                    minQ.add(temp);
                }
                mid =  ((double)maxQ.peek() + minQ.peek()) / 2;
            }
        }

        public double findMedian() {
            return mid;
        }
  • 加入元素:时间复杂度O(lgn);取中位数时间复杂度O(1)
  • 空间复杂度O(n)