剑指offer41.数据流中的中位数

我一开始的想法是既然要找中位数,那肯定要排序,而且这个数据结构肯定要能动态的添加数据的,肯定不能用数组,于是我想到了用优先队列,它自己会排序都不用我写,所以addNum方法直接调用就可以,但是找中位数就很麻烦,它不能根据下标访问,于是我写了一个很屎的方法,把它poll到数组里面找到中位数后,再从数组放到优先队列中,代码如下:

java 复制代码
class MedianFinder {
    private PriorityQueue<Integer> priorityQueue;
    /** initialize your data structure here. */
    public MedianFinder() {
       this.priorityQueue = new PriorityQueue<Integer>();
    }
    
    public void addNum(int num) {
        priorityQueue.add(num);
    }
    
    public double findMedian() {
       int size = priorityQueue.size();
       int[] arr = new int[size];
       for(int i=0;i<size;i++){
           arr[i] = priorityQueue.poll();
       }
       for(int i=0;i<size;i++){
           priorityQueue.add(arr[i]);
       }
       if(size%2 != 0){
           return (double)arr[((size+1)/2) -1];
       }else{
           double res = (arr[(size/2)-1] + arr[size/2])/2.0;
           return res;
       }
    }
}

但是它超时了,题目的测试用例非常大,我就想肯定是找中位数的时候太繁琐了,于是就想到了用LinkedList,可以通过下标获取中位数,只需要自己写一个排序,我就想到用add(int index, E element)方法,每次添加的时候找到添加的位置,这样就一直是有序的,复杂度是O(n),于是我又写了如下代码:

java 复制代码
class MedianFinder {
    private LinkedList<Integer> list;
    /** initialize your data structure here. */
    public MedianFinder() {
       this.list = new LinkedList<Integer>();
    }
    
    public void addNum(int num) {
        int size = list.size();
        if(size == 0){
            list.add(num);
            return;
        }else{
            for(int i =0;i<size;i++){
                if(i==0 && num <= list.get(0)){
                    list.addFirst(num);
                    break;
                }else if(i==size-1 && num >= list.get(size-1)){
                        list.addLast(num);
                        break;
                }else if(num >= list.get(i) && num <= list.get(i+1)){
                    list.add(i+1, num);
                    break;
                }else{
                    continue;
                }
            }
        }
    }
    
    public double findMedian() {
         int size = list.size();
         return size%2 !=0 ? list.get(((size+1)/2)-1)*1.0 : (list.get((size/2)-1)+list.get(size/2))/2.0;
    }
}

但是tmd我没想到这也能超时,真进行了5000次的调用,然后我又试了一下用二分查找添加,不行,看题解了。题解看完我只想说一个字,妙!他也是用的优先队列,但是他用了两个优先队列,一个queMax存大于中位数的数,queMin存小于等于中位数的数,如果总数的奇数,那么中位数就是queMin的队头,如果总数是偶数那么中位数就是queMin和queMax的队头的平均值,添加的时候如果num小于等于中位数就加到queMin中,这时候新的中位数可能会小于原来的中位数,就要把queMin的对头放到queMax中,如果num大于中位数同理。

java 复制代码
class MedianFinder {
    private PriorityQueue<Integer> queMin;
    private PriorityQueue<Integer> queMax;
    /** initialize your data structure here. */
    public MedianFinder() {
       queMin = new PriorityQueue<Integer>((a, b) -> (b - a));
       queMax = new PriorityQueue<Integer>((a, b) -> (a - b));
    }
    
    public void addNum(int num) {
        if(queMin.isEmpty() || num <= queMin.peek()){
            queMin.offer(num);
            if(queMax.size() + 1 < queMin.size()){
                queMax.offer(queMin.poll());
            }
        }else{
            queMax.offer(num);
            if(queMax.size() > queMin.size()){
                queMin.offer(queMax.poll());
            }
        }
    }
   
    public double findMedian() {
         if(queMin.size() > queMax.size()){
             return queMin.peek();
         }
         return (queMax.peek() + queMin.peek()) / 2.0;
    }
}

需要注意的是两个队列的排序方法不一样,queMin是队头最大,从大到小排,queMax是队头最小,从小到大排。所以看到两个优先队列的初始化方法是不一样的,lambda表达式中queMin的返回结果是b-a,而queMax是a-b。

相关推荐
嫩萝卜头儿5 分钟前
2 - 复杂度收尾 + 链表经典OJ
数据结构·算法·链表·复杂度
c++之路14 分钟前
C++20概述
java·开发语言·c++20
星马梦缘17 分钟前
算法设计与分析 作业二 答案与解析
算法·图论·dfs·bfs·floyd-warshall·bellman_ford·多源最短路
玛丽莲茼蒿18 分钟前
Leetcode hot100 每日温度【中等】
算法·leetcode·职场和发展
Championship.23.2418 分钟前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
cjp56025 分钟前
009.UG二次开发,任务环境草图优化3(高级功能生成直线)
算法
橘子海全栈攻城狮33 分钟前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
样例过了就是过了37 分钟前
LeetCode热题100 分割等和子集
数据结构·c++·算法·leetcode·动态规划
逻辑驱动的ken39 分钟前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
木木_王1 小时前
嵌入式Linux学习 | 数据结构 (Day05) 栈与队列详解(原理 + C 语言实现 + 实战实验 + 易错点剖析)
linux·c语言·开发语言·数据结构·笔记·学习