LeetCode295——数据流的中位数

    xiaoxiao2025-03-21  29

    我的LeetCode代码仓:https://github.com/617076674/LeetCode

    原题链接:https://leetcode-cn.com/problems/find-median-from-data-stream/

    题目描述:

    知识点:优先队列、堆

    思路:用一个最大堆有一个最小堆将数据分成两部分

    最终,我们希望得到的数据分布结果是这样的:

    如果数据是偶数个,那么最大堆left和最小堆right中的元素个数相等,且left的堆顶元素小于等于right的堆顶元素,那么我们可以取这两个堆顶元素的平均值作为中位数。

    如果数据是奇数个,那么最大堆left比最小堆right中的元素少一个,且left的堆顶元素小于等于right的堆顶元素,那么我们可以取right的堆顶元素作为中位数。

    那么问题来了,我们怎么样往这两个堆里添加数据才能保证得到上述分布结果呢

    (1)如果left和right都是空,即添加第一个数据的情况,直接将该数据添加进right里即可。

    (2)如果left为空,right不为空,此时我们需要继续分类讨论:

            a:如果新数据num小于等于right的堆顶数据,我们直接将该数据添加进left里即可。

            b:否则,为了保持两个堆间的数据大小关系以及数据数目的平衡关系,我们需要将right的堆顶数据弹出并添加进left里,再将新数据添加进right里。

    (3)如果left和right均不为空,此时我们仍然需要分类讨论:

            a:如果此时left和right的数据数目相等,我们需要保证当数据是奇数个时,right堆的元素个数比left堆的元素个数多一个:

                    a-1:如果新数据num小于left的堆顶数据,我们需要将left的堆顶数据弹出并添加进right里,再将新数据num添加进left里。

                    a-2:否则,我们直接将该数据添加进right里即可。

            b:如果此时left和right的数据数目不等,根据前面的过程,此时显然有right堆的元素个数比left堆的元素个数多一个,但我们不应该将这个差距继续扩大,因此:

                    b-1:如果新数据num大于right的堆顶元素,将right的堆顶元素弹出并添加进left里,再将新数据num添加进right里。

                    b-2:否则,我们直接往left里添加新数据num即可。

    时间复杂度是O(nlogn),其中n为数据总数。空间复杂度是O(n)。

    JAVA代码:

    public class MedianFinder { private PriorityQueue<Integer> left; private PriorityQueue<Integer> right; /** initialize your data structure here. */ public MedianFinder() { left = new PriorityQueue<>((a, b) -> b - a); //最大堆 right = new PriorityQueue<>(); //最小堆 } public void addNum(int num) { if (left.isEmpty() && right.isEmpty()) { right.add(num); } else if (left.isEmpty() && !right.isEmpty()) { if (num <= right.peek()) { left.add(num); } else { left.add(right.poll()); right.add(num); } } else { if (left.size() == right.size()) { if (num < left.peek()) { right.add(left.poll()); left.add(num); } else { right.add(num); } } else { if (num > right.peek()) { left.add(right.poll()); right.add(num); } else { left.add(num); } } } } public double findMedian() { int size = left.size() + right.size(); if (0 == size % 2) { return (left.peek() + right.peek()) / 2.0; } else { return right.peek(); } } }

    LeetCode解题报告:

    进阶一:数据流中所有整数都在 0 到 100 范围内

    用一个数组保存每个数字出现的次数。

    时间复杂度和空间复杂度均是O(1)。

    JAVA代码:

    public class MedianFinder { int[] count; int total; /** initialize your data structure here. */ public MedianFinder() { count = new int[101]; total = 0; } public void addNum(int num) { count[num]++; total++; } public double findMedian() { if (0 == total % 2) { return (findKthNumber(total / 2) + findKthNumber(total / 2 + 1)) / 2.0; } else { return findKthNumber(total / 2 + 1); } } private int findKthNumber(int k) { int index = 0; for (int i = 0; i < 101; i++) { index += count[i]; if (index >= k) { return i; } } return -1; } }

    进阶二:数据流中 99% 的整数都在 0 到 100 范围内

    和进阶一同样的思路,只不过将大于100的数除去即可,因为有99%的整数都在0到100范围内,所以中位数一定是0到100范围内的某个数,只需根据大于100的数的个数在进阶一的基础上调整即可。

    时间复杂度和空间复杂度均是O(1)。

    public class MedianFinder { int[] count; int total; int greaterThan100; /** initialize your data structure here. */ public MedianFinder() { count = new int[101]; total = 0; greaterThan100 = 0; } public void addNum(int num) { if (num <= 100) { count[num]++; } else { greaterThan100++; } total++; } public double findMedian() { int temp = total - greaterThan100 * 2; if (0 == temp % 2) { return (findKthNumber(greaterThan100 + temp / 2) + findKthNumber(greaterThan100 + temp / 2 + 1)) / 2.0; } else { return findKthNumber(greaterThan100 + temp / 2 + 1); } } private int findKthNumber(int k) { int index = 0; for (int i = 0; i < 101; i++) { index += count[i]; if (index >= k) { return i; } } return -1; } }

     

    最新回复(0)