LeetCode每日一题(2021-2-3 滑动窗口中位数)

news/2024/5/19 2:32:12 标签: leetcode, java, 算法, 堆排序

LeetCode每日一题(2021-2-3 滑动窗口中位数)

题目描述

中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。

例如:

● [2,3,4],中位数是 3
● [2,3],中位数是 (2 + 3) / 2 = 2.5
给你一个数组 nums,有一个长度为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。

示例
在这里插入图片描述

解题思路

  话说力扣上个月的每日一题都是并查集,这个月看来是要和滑动窗口杠上了,不过这种按标签练习题目的效果还是挺不错的,能比较牢固地掌握这个知识点。

  首先最容易想到的是暴力法。用两个整数作为双指针构建滑动窗口,依次把每次移动后的滑动窗口的中位数求出(滑动窗口大小是偶数和奇数的情况分开讨论),存放在结果数组。不过,注意中位数是有序序列的中间数,因此,每次构造一个辅助数组复制滑动窗口的内容进行排序。

代码如下:

java">class Solution {
    public double[] medianSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        double[] ans = new double[n - k + 1];
        int l = 0, r = k - 1; //双指针指向滑动窗口两端
        int idx = 0;
        for(; r < n; r++){ //移动滑动窗口直到窗口右端抵达终点
            int[] temp = new int[k]; //构造一个辅助数组用于排序滑动窗口内容
            for(int i = 0; i < k; i++){
                temp[i] = nums[l + i];
            }
            Arrays.sort(temp); //排序
            if(k % 2 == 0){ //滑动窗口大小为偶数
                int mid = k / 2;
                ans[idx] = ((double)temp[mid] + (double)temp[mid - 1]) / 2;
            }
            else{ //滑动窗口大小为奇数
                int mid = k / 2;
                ans[idx] = temp[mid];
            }
            idx++;
            l++;
        }
        return ans;
    }
}

  注意,滑动窗口大小为偶数时,中位数是中间两个数的平均值,这里要把int类型的数字做一个强制转换,变成doube,否则会造成精度丢失(例如2+3 / 2,int类型情况下等于2,doublel类型情况下等于2.5)。这种方法时间复杂度太高,偶尔还会超时。
在这里插入图片描述
在这里插入图片描述
  暴力法耗时的一个重要原因就是每次都要对子区间进行排序,如果我们能找到一个内部本就是有序的数据结构,那么问题就能简单许多。这里不得不说要是用C++的multiset结构,那么二三十行代码就能解决问题,而且速度很快,java实现相对就要麻烦不少,苦逼java

  这里用java构建两个堆,一个大顶堆存放小于中位数的数字,一个小顶堆存放大于中位数的数字,那么堆顶(滑动窗口大小为奇数)或者两个堆顶的平均数(滑动窗口大小为偶数)就是中位数。注意,这个过程中需要一直控制两个堆的相对大小,要么大顶堆大小 = 小顶堆大小,要么大顶堆大小 + 1 = 小顶堆大小,这样才能保证可以一直用堆顶元素求得中位数。java实现代码如下:

java">class Solution {
    public double[] medianSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        double[] ans = new double[n - k + 1];
        PriorityQueue<Double> smallHeap = new PriorityQueue<>(); //构建小顶堆
        PriorityQueue<Double> bigHeap = new PriorityQueue<>(Collections.reverseOrder()); //构建大顶堆
        //对两个堆进行初始化,先放入第一个滑动窗口的数字
        int mid = k / 2;
        for(int i = 0; i < k; i++){
            bigHeap.add((double)nums[i]);
        }
        for(int i = mid; i < k; i++){
            smallHeap.add(bigHeap.poll());
        }
        ans[0] = k % 2 == 0 ? (bigHeap.peek() + smallHeap.peek()) / 2 : smallHeap.peek();
        for(int i = k; i < n; i++){
            double curNum = (double)nums[i]; //当前遍历到的数字
            double removeNum = (double)nums[i - k]; //当前将要移除的数字
            //若当前数字小于等于大顶堆的堆顶,则加入大顶堆,否则加入小顶堆
            if(!bigHeap.isEmpty() && curNum <= bigHeap.peek()){
                bigHeap.add(curNum);
            }
            else{
                smallHeap.add(curNum);
            }
            //若当前要移除数字小于等于大顶堆的堆顶,则从大顶堆中移除,否则从小顶堆中移除
            if(!bigHeap.isEmpty() && removeNum <= bigHeap.peek()){
                bigHeap.remove(removeNum);
            }
            else{
                smallHeap.remove(removeNum);
            }
            //控制两个堆的相对大小
            while(bigHeap.size() > smallHeap.size()){
                smallHeap.add(bigHeap.poll());
            }
            while(bigHeap.size() + 1 < smallHeap.size()){
                bigHeap.add(smallHeap.poll());
            }
            ans[i - k + 1] = k % 2 == 0 ? (bigHeap.peek() + smallHeap.peek()) / 2 : smallHeap.peek();
        }
        return ans;
    }
}

可以看出,时间快了非常多。
在这里插入图片描述


http://www.niftyadmin.cn/n/1796542.html

相关文章

如何下载百度地图

百度地图有四种&#xff0c;一种是基于卫星地图的百度卫星地图&#xff0c;一种是基于街道图的百度电子地图&#xff0c;另外两种分别是电子地图大字体地图和卫星地图大字体地图&#xff0c;适合做广告喷绘打印等。这里讲解如何下载基于街道图的百度电子地图。 1.选择地图 打…

利用优秀的.NET界面控件,打造新潮的界面效果

一直以来&#xff0c;做.NET共享小软件的界面一般采用IrisSkin这个比较不错的皮肤控件来美化界面效果&#xff0c;方便易用&#xff0c;界面效果也还可以。如下面我做的QQ搜通天的界面效果如下&#xff1a;http://www.iqidi.com/Download/qqcollector1.png &#xff08;不贴图了…

分享一个谷歌浏览器插件 JSON-handle

没有插件之前是这样的。 加了特效之后是这样的&#xff0c;如下图&#xff1a; 看起来都舒服多了。 直接上下载地址和安装方法。 http://jsonhandle.sinaapp.com/ 转载于:https://www.cnblogs.com/gejings/p/5306993.html

如何证明地球是圆的呢

在遥远的古代&#xff0c;希腊人就断定大地是球体的&#xff0c;但没有充足的证据。1519年9月&#xff0c;葡萄牙人麦哲伦带领着一支船队从西班牙出发&#xff0c;一直向西航行&#xff0c;经过三年的时间又回到起点西班牙&#xff0c;这是人类的第一次环球航行&#xff0c;这就…

Ubuntu下编译Linux内核常见错误总结

Ubuntu下编译Linux内核常见错误总结 最近在做linux内核分析课程的大作业&#xff0c;涉及到了内核的编译&#xff0c;遇见了不少问题&#xff0c;这里做一个整理总结。 ● 编译内核执行make menuconfig命令时提示错误fatal error: curses.h: 没有那个文件或目录 原因&#x…

Silverlight4 RIA应用开发系列课程(12):Silverlight中调用RIA Service

Silverlight4 RIA应用开发系列课程(12)&#xff1a;Silverlight中调用RIA Service 为了开发各种数据库应用&#xff0c;微软为Silverlight提供了专门的数据访问特性RIA Service&#xff0c;本节课程我们这一数据访问模型&#xff0c;并演示如何在Silverlight中对它进行调用和整…

如何证明地球在自转?

人在地球上放眼天外&#xff0c;等于巡视宇宙&#xff0c;可以看到无数像银河那样的星系。那么&#xff0c;在没有"高科技"的情况下&#xff0c;古人是如何知道地球在自转呢&#xff1f;古人认为太阳东升西落的原因是太阳、月亮、星星都在围绕地球转动&#xff0c;即…

LeetCode每日一题(2021-2-5 尽可能使字符串相等)

LeetCode每日一题&#xff08;2021-2-5 尽可能使字符串相等&#xff09; 题目描述 给你两个长度相同的字符串&#xff0c;s 和 t。 将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销&#xff08;开销可能为 0&#xff09;&#xff0c;也就是两个字符的…