首页 > 其他分享 >【剑指Offer】63、数据流中的中位数

【剑指Offer】63、数据流中的中位数

时间:2023-08-24 22:24:21浏览次数:25  
标签:Offer maxHeap 中位数 插入 63 minHeap 数据 堆中

【剑指Offer】63、数据流中的中位数

题目描述:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

解题思路:

首先要正确理解此题的含义,数据是从一个数据流中读出来的,因此数据的数目随着时间的变化而增加。对于从数据流中读出来的数据,当然要用一个数据容器来保存,也就是当有新的数据从流中读出时,需要插入数据容器中进行保存。那么我们需要考虑的主要问题就是选用什么样的数据结构来保存。

方法一:用数组保存数据。数组是最简单的数据容器,如果数组没有排序,在其中找中位数可以使用类比快速排序的partition函数,则插入数据需要的时间复杂度是O(1),找中位数需要的复杂度是O(n)。除此之外,我们还可以想到用直接插入排序的思想,在每到来一个数据时,将其插入到合适的位置,这样可以使数组有序,这种方法使得插入数据的时间复杂度变为O(n),因为可能导致n个数移动,而排序的数组找中位数很简单,只需要O(1)的时间复杂度。

方法二:用链表保存数据。用排序的链表保存从流中的数据,每读出一个数据,需要O(n)的时间找到其插入的位置,然后可以定义两个指针指向中间的结点,可以在O(1)的时间内找到中位数,和排序的数组差不多。

方法三:用二叉搜索树保存数据。在二叉搜索树种插入一个数据的时间复杂度是O(logn),为了得到中位数,可以在每个结点增加一个表示子树结点个数的字段,就可以在O(logn)的时间内找到中位数,但是二叉搜索树极度不平衡时,会退化为链表,最差情况仍需要O(n)的复杂度。

方法四:用AVL树保存数据。由于二叉搜索树的退化,我们很自然可以想到用AVL树来克服这个问题,并做一个修改,使平衡因子为左右子树的结点数之差,则这样可以在O(logn)的时间复杂度插入数据,并在O(1)的时间内找到中位数,但是问题在于AVL树的实现比较复杂。

方法五:最大堆和最小堆。我们注意到当数据保存到容器中时,可以分为两部分,左边一部分的数据要比右边一部分的数据小。如下图所示,P1是左边最大的数,P2是右边最小的数,即使左右两部分数据不是有序的,我们也有一个结论就是:左边最大的数小于右边最小的数。

因此,我们可以有如下的思路:用一个最大堆实现左边的数据存储,用一个最小堆实现右边的数据存储,向堆中插入一个数据的时间是O(logn),而中位数就是堆顶的数据,只需要O(1)的时间就可得到。
  而在具体实现上,首先要保证数据平均分配到两个堆中,两个堆中的数据数目之差不超过1,为了实现平均分配,可以在数据的总数目是偶数时,将数据插入最小堆,否则插入最大堆。

此外,还要保证所有最大堆中的数据要小于最小堆中的数据。所以,新传入的数据要和最大堆中最大值或者最小堆中的最小值比较。当总数目是偶数时,我们会插入最小堆,但是在这之前,我们需要判断这个数据和最大堆中的最大值哪个更大,如果最大值中的最大值比较大,那么将这个数据插入最大堆,并把最大堆中的最大值弹出插入最小堆。由于最终插入到最小堆的是原最大堆中最大的,所以保证了最小堆中所有的数据都大于最大堆中的数据。

总结:

编程实现(Java):

import java.util.*;
public class Solution {
    /*
    思路:最大堆和最小堆
    */
    PriorityQueue<Integer> minHeap=new PriorityQueue<>();
    PriorityQueue<Integer> maxHeap=new PriorityQueue<>(new Comparator<Integer>(){
        public int compare(Integer o1,Integer o2){
            return o2-o1;
        }
    });
    int count=0;
    public void Insert(Integer num) {
        count++;
        if(count%2==0){   //偶数,插入最小堆
            if(!maxHeap.isEmpty() && num<maxHeap.peek()){ //如果num小于最大堆,那么先插入最大堆
                maxHeap.add(num);
                num=maxHeap.poll();
            }
            minHeap.add(num);
        }else{ //奇数,插入最大堆
            if(!minHeap.isEmpty() && num>minHeap.peek()){
                minHeap.add(num);
                num=minHeap.poll();
            }
             maxHeap.add(num);
        }
    }

    public Double GetMedian() {
        if(minHeap.size()==maxHeap.size())
            return (minHeap.peek()+maxHeap.peek())/2.0;
        else if(maxHeap.size()>minHeap.size())
            return maxHeap.peek()/1.0;
        else
            return minHeap.peek()/1.0;
    }

}

或者可以使用java8引入的lamda表达式进行实现:

import java.util.*;
public class Solution {
    private PriorityQueue<Integer> maxHeap=new PriorityQueue<>((a,b)->b-a);  //大顶堆用于存放左边的数据
    private PriorityQueue<Integer> minHeap=new PriorityQueue<>();  //最小堆存右边的数据
    private int count=0; //当前数据流的数据个数 

    public void Insert(Integer num) { //第奇数个插入最大堆,偶数个插入最小堆
        count++;
        if(count%2==0){  //偶数个,插入最小堆,但是插入最小堆的必须大于左边所有的,所以必须大于左边的最大值,否则换一下
            if(!maxHeap.isEmpty() && num<maxHeap.peek()){
                maxHeap.add(num);
                num=maxHeap.poll();  //和左边最大值交换
            }
            minHeap.add(num);
        }else{  //奇数个,插入最大堆,同理
            if(!minHeap.isEmpty() && num>minHeap.peek()){
                minHeap.add(num);
                num=minHeap.poll();
            }
            maxHeap.add(num);
        }
    }

    public Double GetMedian() {
        if(maxHeap.size()>minHeap.size())
            return maxHeap.peek()/1.0;
        else if(maxHeap.size()<minHeap.size())
            return minHeap.peek()/1.0;
        else
            return (maxHeap.peek()+minHeap.peek())/2.0;
    }
}

标签:Offer,maxHeap,中位数,插入,63,minHeap,数据,堆中
From: https://www.cnblogs.com/bujidao1128/p/17655298.html

相关文章

  • 《剑指Offer》-60-n 个骰子的点数
    打印出n个骰子所能扔出的所有点数的概率思路dp[i][j]表示i个骰子,投出j的概率而概率=点数出现的次数/总次数而i个骰子掷出j的次数=i-1个骰子掷出j-1的次数+i-1个骰子掷出j-2的次数+…+i-1个骰子掷出j-6的次数,因为这个单独的骰子能掷......
  • 【剑指Offer】46、圆圈中最后剩下的数
    【剑指Offer】46、圆圈中最后剩下的数题目描述:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报......
  • 剑指 Offer 47. 礼物的最大价值(中等)
    题目:classSolution{public:intmaxValue(vector<vector<int>>&grid){if(grid.empty())return0;//要考虑棋盘为空的情况直接返回0vector<vector<int>>dp(grid.size(),vector(grid[0].size(),0));//定义一个和棋盘同样大小的dp......
  • 剑指 Offer 63. 股票的最大利润(中等)
    题目:classSolution{public:intmaxProfit(vector<int>&prices){if(prices.empty())return0;//要考虑数组为空的情况vector<vector<int>>dp(prices.size(),vector<int>(2,0));//确定动态数组大小和下表含义dp[i][j]:第i天j状态......
  • 【剑指Offer】45、扑克牌顺子
    【剑指Offer】45、扑克牌顺子题目描述:LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh......
  • 【剑指Offer】41、和为S的连续正数序列
    【剑指Offer】41、和为S的连续正数序列题目描述:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,......
  • 【剑指Offer】42、和为S的两个数字
    【剑指Offer】42、和为S的两个数字题目描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。输出描述:对应每个测试案例,输出两个数,小的先输出。解题思路:对于本题,比上一题简单一些。看到题目,我们的第......
  • 剑指 Offer 10- I. 斐波那契数列(简单)
    题目:classSolution{//动态规划public:intfib(intn){if(n<=1)returnn;vector<int>dp(2,0);//确定dp数组以及下标的含义dp[0]=0;//dp数组初始化dp[1]=1;for(inti=2;i<=n;i++){//递推顺序从......
  • 剑指 Offer 41. 数据流中的中位数(困难)
    题目:classMedianFinder{//暴力解法:每添加一个数字后用sort进行排序,然后返回中位数,时间复杂度:nlogn,会超时。因此采用**二分法查找并进行插入的方法**。时间复杂度:lognpublic:vector<int>nums;//初始化一个当前数组/**initializeyourdatastructure......
  • 2063:【例1.4】牛吃牧草
    2063:【例1.4】牛吃牧草时间限制:1000ms      内存限制:65536KB提交数:81880   通过数:50598【题目描述】有一个牧场,牧场上的牧草每天都在匀速生长,这片牧场可供15头牛吃20天,或可供20头牛吃10天,那么,这片牧场每天新生的草量可供几头牛吃1天?【输入】(无)......