• Leetcode 295. 数据流的中位数


    1.题目要求

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

    例如,

    [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

    进阶:

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

    2.解题思路

    堆是一个非常重要的数据结构,堆排序在C++中的实现为优先级队列(Priority_queue),关于这一点,我的另一篇博文 "Leetcode 703. 数据流中的第K大元素"  有更详细提到,这里不做重复。

    LeetCode网站把这一道划分在“堆”一类中,也是提醒我们使用堆结构。这道题很巧妙,我是听了算法课(牛客网的左程云大牛)的讲解才弄明白。这里的代码是自己听懂了思路,独立写出来的。

    关键思路:建立两个堆(使用priority_queue实现),一个大根堆,一个小根堆。

              (1)一个大根堆保存所有整数中较小的1/2;一个小根堆保存所有整数中较大的1/2
              (2)并且,依次添加元素过程中,两个堆元素个数的差的绝对值不能超过1

                     这样,两个堆建立好了以后,

              (1)如果输入的元素个数 n 是偶数,则两个堆的元素个数相等,分别取大根堆的顶和小根堆的顶,取平均值,即是所求的整个数据流的中位数;

              (2)如果输入的元素个数 n 是奇数,则必有一个堆的元素个数为(n/2+1),返回这个堆的顶,即为所求的中位数。

    3.我的代码

            个人比较喜欢写段落注释行注释,因为这样自己一年之后还能快速看懂,当然也方便他人,特别是一起刷题的伙伴,轻松看懂。

            更多的细节讲解里都在注释里。如有错误的地方,欢迎多指正。

            代码通过所有测试案例的时间为124ms。        

    class MedianFinder {
    public:
        /** initialize your data structure here. */
        MedianFinder() {
            
        }
        
        void addNum(int num) {
            /*建立两个堆:(1)一个大根堆,保存所有整数中较小的1/2;一个小根堆,保存所有整数中较大的1/2;
              (2)并且,依次添加元素过程中,两个堆大小的差的绝对值不能超过1; */
            
            //第一元素加入大根堆
            if(heap1.size()==0){
                heap1.push(num);
                return;
            }
            
            if(num<=heap1.top()){
                //第二个元素比大根堆的顶小
                heap1.push(num);
                    
                 //大根堆元素过多
                if(heap1.size()-heap2.size()>1)
                {
                    int temp = heap1.top();
                    heap1.pop();
                    heap2.push(temp);//大根堆弹出顶到小根堆     
                }
                
            }
            else{
                //第二个元素比大根堆的顶大,直接进入小根堆
                heap2.push(num);
                
                //小根堆元素过多
                if(heap2.size()-heap1.size()>1)
                {
                    int temp = heap2.top();
                    heap2.pop();
                    heap1.push(temp);//小根堆弹出顶到大根堆 
                }
            }
            
        }
        
        double findMedian() {
            //输入的元素为奇数个
            if(heap1.size() > heap2.size())
                return heap1.top();
            else if(heap1.size() < heap2.size())
                return heap2.top();
            
            //输入的元素个数为偶数
            else
                return (heap1.top()+heap2.top())/2.0; 
               //取大根堆、小根堆的堆顶元素取平均值,即为所求全局中位数
        }
        
    private:
        priority_queue<int> heap1;//默认,大根堆
        priority_queue<int,vector<int>,greater<int>> heap2;//小根堆(升序序列)
        
    };
    
    /**
     * Your MedianFinder object will be instantiated and called as such:
     * MedianFinder obj = new MedianFinder();
     * obj.addNum(num);
     * double param_2 = obj.findMedian();
     */

      

    4.用时更少的示例代码

     这是我提交解答后,查看细节,看到的Leetcode官网上提交的关于这道题运行时间最短(96ms)的示例代码。

     LeetCode上刷好多速度排名第一的代码中都有一段类似的代码,就是下面代码中的第一段代码——优化C++的IO速度。

    /*一般地,C++的运行速度不如C的,主要原因是C++的输入输出流兼容了C的输入输出,因此,C++的速度才会变慢,
    如果去掉C++的输入输出的兼容性的话,速度就和C的差不多了
    */
    static const auto __ = []() { // turn off sync std::ios::sync_with_stdio(false); // untie in/out streams std::cin.tie(nullptr); return nullptr; }(); class MedianFinder { public: /** initialize your data structure here. */ //使用vector实现两个堆,而不是priority_queue vector<int> maxheap; vector<int> minheap; bool flag = true; MedianFinder() { } void addNum(int num) { if(flag){ //构建小根堆 if(minheap.size()>0&&num>minheap[0]){ minheap.push_back(num); push_heap(minheap.begin(),minheap.end(),greater<int>()); num = minheap[0]; pop_heap(minheap.begin(),minheap.end(),greater<int>()); minheap.pop_back(); } maxheap.push_back(num); push_heap(maxheap.begin(),maxheap.end(),less<int>()); flag=false; }else{ //构建大根堆 if(maxheap.size()>0&&num<maxheap[0]){ maxheap.push_back(num); push_heap(maxheap.begin(),maxheap.end(),less<int>()); num = maxheap[0]; pop_heap(maxheap.begin(),maxheap.end(),less<int>()); maxheap.pop_back(); } minheap.push_back(num); push_heap(minheap.begin(),minheap.end(),greater<int>()); flag=true; } } double findMedian() { if(maxheap.size()<1&&minheap.size()<1) return 0; if(flag){ return (maxheap[0]+minheap[0])/2.0; }else{ return maxheap[0]; } } }; /** * Your MedianFinder object will be instantiated and called as such: * MedianFinder obj = new MedianFinder(); * obj.addNum(num); * double param_2 = obj.findMedian(); */

    参考博客:

          https://blog.csdn.net/xiaosshhaa/article/details/78136032    std::ios::sync_with_stdio(false); cin.tie(0);

  • 相关阅读:
    如何完全删除Linux应用
    IP地址获取工具类
    日期处理工具类
    Cookies的工具类
    权限管理系统学习笔记
    SpringBoot中JPA的一些基本操作
    Mysql和Java的数据类型对应表
    MySQL中的tinyint
    幂等性浅谈
    链接
  • 原文地址:https://www.cnblogs.com/paulprayer/p/9884332.html
Copyright © 2020-2023  润新知