力扣:239. 滑动窗口最大值

题目链接239. 滑动窗口最大值
题目

给你一个整数数组 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 x k)。这时可以想到用大顶堆(优先级队列)来存放窗口里的k个数字,这样就可以知道最大的值是多少了,但是这其中存在一个问题:窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值,所以不能用大顶堆
此时考虑到窗口是移动的,可以用一个队列来实现,因为队列是先进先出的。对于这个队列,会随着窗口的移动,调用que.pop(滑动窗口移除元素的数值),que.push(窗口添加元素的数值),然后que.front()返回我们需要的最大值。我们没有现成的这种数据结构,所以我们需要自己实现这个队列。
我们需要的队列结构:

class MyQueue {
	void pop(int value) {}
	void push(int value) {}
	int front() {}
}

分析一下,队列里的元素一定是要排序的,而且最大值放在出队口。放入窗口元素进队列的时候,需要弹出元素,那么怎么才能弹出需要移除的元素呢,这个元素可不一定是最大值。
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里的最大值的元素就可以了,同时保证队里的元素数值是由大到小的。
那么这个维护元素单调(递减或递增)的队列就是单调队列。C++里没有直接支持单调队列,需要我们自己来实现。
单调队列可不简单的对窗口里的元素数值排序,如果只是排序,那和优先级队列没有什么区别。
下面是就本题示例来讲解实现思路:
对于窗口里的元素,只需要维护最大值以及可能成为最大值的元素就够了,保持单调队列里单调递减,此时队列出口元素就是窗口的最大元素。
设计单调对立时,pop和push操作要保持如下规则:
(1)pop(val):如果窗口移除的元素val等于单调队列的出口元素,则弹出元素,否则不进行任何操作;
(2)push(val):如果push的元素val大于入口元素的数值,那么就将队列的入口元素弹出,直到push的元素数值不大于队列入口元素的数值为止。
保持上面的规则,每次窗口移动的时候,que.front()就是我们需要的窗口最大值。
代码(c++)

class Solution {
    //定义一个单调队列,队列里元素从大到小排列
    //deque:出口 <---------- 入口
    class MyQueue {
        deque<int> que; //用deque来实现单调队列
    public:
        //在每次弹出之前,比较当前要弹出的数值是否等于队列出口元素的数值,相等则弹出
        //同时pop之前判断队列是否为空
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        //如果要push的数值value大于单调队列入口元素的数值,则弹出入口元素
        //直至push的数值小于队列入口元素的数值,这样就保证了队列里的数值是从大到小
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);
        }
        //查询队列里的最大值(队列出口元素就是最大值)
        int front() {
            return que.front();
        }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue myque;
        vector<int> res;
        for (int i = 0; i < k; ++i) {   //先往队列里放入前k个元素
            myque.push(nums[i]);
        }
        res.push_back(myque.front());   //res里存放窗口里的最大值
        for (int i = k; i < nums.size(); ++i) {
            myque.pop(nums[i - k]); //移除窗口最前面的元素
            myque.push(nums[i]);    //加入元素到窗口最后面
            res.push_back(myque.front());   //记录窗口里的最大值
        }
        return res;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
    nums中的每个元素最多被push_back和pop_back各一次。
  • 空间复杂度: O ( k ) O(k) O(k)
    定义了一个辅助队列。

注意:单调队列不是一成不变的,不同场景不同写法,但有一点确定的是,单调队列一定是单调递增或者单调递减的原则。本题单调队列仅适用于本题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追梦偏执狂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值