首页 > 其他分享 >【239. 滑动窗口最大值 困难】

【239. 滑动窗口最大值 困难】

时间:2024-12-18 12:29:54浏览次数:8  
标签:窗口 队列 最大值 元素 que 239 push 滑动 单调

题目:

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 --------最大值
[1 3 -1] -3 5 3 6 7 --------3
1 [3 -1 -3] 5 3 6 7 --------3
1 3 [-1 -3 5] 3 6 7 --------5
1 3 -1 [-3 5 3] 6 7 --------5
1 3 -1 -3 [5 3 6] 7 --------6
1 3 -1 -3 5 [3 6 7] --------7

示例 2:
输入:nums = [1], k = 1
输出:[1]

提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length


思路:

这是使用单调队列的经典题目。

难点是如何求一个区间里的最大值呢? (这好像是废话),暴力一下不就得了。

暴力方法,遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法。

有的同学可能会想用一个大顶堆(优先级队列)来存放这个窗口里的k个数字,这样就可以知道最大的最大值是多少了, 但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。

此时我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。

这个队列应该长这个样子:

class MyQueue {
public:
    void pop(int value) {
    }
    void push(int value) {
    }
    int front() {
        return que.front();
    }
};

每次窗口移动的时候,调用que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。

这么个队列香不香,要是有现成的这种数据结构是不是更香了!

其实在C++中,可以使用 multiset 来模拟这个过程,文末提供这个解法仅针对C++,以下讲解我们还是靠自己来实现这个单调队列。

然后再分析一下,队列里的元素一定是要排序的,而且要最大值放在出队口,要不然怎么知道最大值呢。

但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素。

那么问题来了,已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢。

大家此时应该陷入深思…

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列

不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。

来看一下单调队列如何维护队列里的元素。

动画如下:
在这里插入图片描述
对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。

此时大家应该怀疑单调队列里维护着{5, 4} 怎么配合窗口进行滑动呢?

设计单调队列的时候,pop,和push操作要保持如下规则:

  • pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  • push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
    保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。

为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下:
在这里插入图片描述
那么我们用什么数据结构来实现这个单调队列呢?

使用deque最为合适,常用的queue在没有指定容器的情况下,deque就是默认底层容器。

基于刚刚说过的单调队列pop和push的规则,代码不难实现,如下:

class MyQueue{  //  单调队列(从大到小)
    public:
        deque<int> que; //  使用deque来实现单调队列,也就是该题的滑动窗口
        
        //  滑动窗口时弹出窗口第一个元素时用的
        //  每次弹出的时候,如果que非空且当前要弹出的数值等于队列出口元素的数值,则弹出
        void pop(int value){
            if(!que.empty() && value == que.front()){
                que.pop_front();
            }
        }

        //  滑动窗口时填充窗口最后一个元素时用的
        //  如果push的数值大于入口元素的数值,则将入口元素弹出,直到push的数值小于等于队列入口元素的数值为止
        //  这样就保证了队列里的素质是单调从大到小的
        void push(int value){
            while(!que.empty() && value > que.back()){
                que.pop_back();
            }
            que.push_back(value);
        }

        //  队列前端的值front就是窗口中的最大值
        int front(){
            return que.front();
        }
    };

这样我们就用deque实现了一个单调队列,接下来解决滑动窗口最大值的问题就很简单了,直接看代码吧。


代码:

  1. 写法1:基于上述分析实现单调队列
class Solution {
private:
    class MyQueue{  //  单调队列(从大到小)
    public:
        deque<int> que; //  使用deque来实现单调队列,也就是该题的滑动窗口
        
        //  滑动窗口时弹出窗口第一个元素时用的
        //  每次弹出的时候,如果que非空且当前要弹出的数值等于队列出口元素的数值,则弹出
        void pop(int value){
            if(!que.empty() && value == que.front()){
                que.pop_front();
            }
        }

        //  滑动窗口时填充窗口最后一个元素时用的
        //  如果push的数值大于入口元素的数值,则将入口元素弹出,直到push的数值小于等于队列入口元素的数值为止
        //  这样就保证了队列里的素质是单调从大到小的
        void push(int value){
            while(!que.empty() && value > que.back()){
                que.pop_back();
            }
            que.push_back(value);
        }

        //  队列前端的值front就是窗口中的最大值
        int front(){
            return que.front();
        }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;

        //  先将前k个元素放进队列,即滑动窗口
        for(int i = 0; i < k; i++){
            que.push(nums[i]);
        }

        //  result记录当前窗口的最大值
        result.push_back(que.front());
        for(int i = k; i < nums.size(); i++){
            que.pop(nums[i - k]);   //  移除窗口最前面的元素
            que.push(nums[i]);  //  填充窗口末端元素进行滑动
            result.push_back(que.front());  //  记录当前窗口的最大值
        }
        return result;
    }
};
  1. 写法2:基于C++自带容器multiset实现
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        multiset<int> set1; //multiset作为滑动窗口,且其会对元素从小到大排序
        vector<int> result;
        
        //  在遍历原数组的时候,只需要把窗口的头元素加入到multiset中,然后把窗口的尾元素删除即可。因为multiset是有序的,并且提供了*rbegin(),可以直接获取窗口最大值。
        for(int i = 0; i < nums.size(); i++){
            if(i >= k) set1.erase(set1.find(nums[i - k]));
            set1.insert(nums[i]);
            if(i >= k - 1) result.push_back(*set1.rbegin());
        }
        return result;
    }
};

总结:

时间复杂度: O(n)
空间复杂度: O(k)

nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,所以整体的时间复杂度还是 O(n)。

C++中deque是stack和queue默认的底层实现容器,deque是可以两边扩展的,而且deque里元素并不是严格的连续分布的。


参考:

代码随想录

标签:窗口,队列,最大值,元素,que,239,push,滑动,单调
From: https://blog.csdn.net/yuan_2001_/article/details/144557712

相关文章

  • 滑动变阻器的主要特性和参数有哪些?
    滑动变阻器是一种常见的电阻调节装置,其主要特性和参数如下:电阻值范围:滑动变阻器的电阻值范围是指其最小电阻值到最大电阻值之间的范围。这个范围通常由滑动变阻器的设计决定,一般在几欧姆到几千欧姆之间。额定功率:滑动变阻器的额定功率是指其能够承受的最大电功率。如果超过这个......
  • 代码随想录:滑动窗口最大值
    代码随想录:滑动窗口最大值用双端队列写一个单调队列classSolution{public:classbiggerqueue{public:deque<int>target;//intwindows_size;//biggerqueue(intsize){windows_size=size;}//全错了,不能用size来pop掉......
  • MySQL 中 AUTO_INCREMENT 列达到最大值时会发生什么?
    在MySQL中,AUTO_INCREMENT列用于自动生成唯一的数字值,通常用于主键。当AUTO_INCREMENT列达到最大值时,会发生以下几种情况,具体取决于列的数据类型以及MySQL的配置。对于TINYINT类型:最大值:TINYINT的最大值为127(有符号)或255(无符号)。当AUTO_INCREMENT列达到最大值时,如果尝......
  • js带模糊效果的隐藏滑动侧边栏插件
    pushbar.js是一款带模糊效果的js隐藏滑动侧边栏插件。pushbar.js能制作上下左右四个方向的滑动侧边栏效果,并且在侧边栏菜单显示的时候,主页面会带有炫酷的模糊特效。 在线演示 下载 使用方法在页面中引入pushbar.js和pushbar.css文件。<linkhref="dist/css/pushbar.cs......
  • 使用递归实现指定最小值和最大值之间的所有整数求和
    functionsumRangeRecursive(min,max){//基本情况:如果最小值大于最大值,则返回0(空范围)if(min>max){return0;}//递归步骤:返回当前最小值加上剩余范围的和returnmin+sumRangeRecursive(min+1,max);}//示例用法:console.log(sumRangeRecurs......
  • QT自定义控件实践--滑动组件
    概述             本篇文章,会逐步带您了解,如何自定义一个QT的滑动组件操作步骤选择合适的基类继承:我们命名这个自定义控件为MySlipButton,继承自QWidget添加成员变量:根据滑动组件的特性,添加合适的成员变量,如当前值、最小值、最大值、滑块的位置等。......
  • (nice!!!)(LeetCode 热题 100) 76. 最小覆盖子串(哈希表、滑动窗口、双指针)
    题目:76.最小覆盖子串思路:用哈希表来记录字符串t中字符出现的情况。然后用双指针来实现滑动窗口,找到最小的字符串即可。时间复杂度为0(m+n),细节看注释。classSolution{public:stringminWindow(strings,stringt){ //哈希表unordered_map<char......
  • 最大值,最小值,平均值(C语言)
    今天来做一道比较经典的题目求最大值,最小值和平均值,常用于信息统计;以成绩为例:输入n科成绩(浮点数表示),统计其中的最高分,最低分以及平均分。数据范围:1≤n≤100 1≤n≤100  ,成绩使用百分制且不可能出现负数一般要用到数组,选择和循环语句原理:数组存储数据通过循环遍历数......
  • 【滑动窗口】codeforces 1290 A. Mind Control
    题意第一行输入一个正整数\(T(1\leqT\leq1000)\),表示共有\(T\)组测试用例。对于每一组测试用例:第一行输入三个正整数\(n,m,k(1\leqm\leqn\leq3500,0\leqk\leqn-1)\),且保证\(n\)之和不超过\(3500\),第二行输入\(n\)个整数\(a_i(1\leqa_i\leq10^9......
  • (每日一题)Fibonacci数列——<滑动窗⼝>
    1.题⽬链接:WY22Fibonacci数列 2.题⽬描述:3.解法:算法思路:求斐波那契数列的过程中,判断⼀下:何时n会在两个fib数之间。  C++算法代码: #include<iostream>#include<cmath>usingnamespacestd;intmain(){intkey;cin>>key;inta=0,b=1......