[剑指Offer]:滑动窗口的最大值

85 阅读2分钟

文章目录


题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: 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

题解思路

方法一:暴力解法

复杂度分析:

  • 时间复杂度 O ( n u m s . s i z e ( ) ∗ k ) O(nums.size() * k) O(nums.size()∗k)
  • 空间复杂度 O ( n ) O(n) O(n)

代码实现:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size() < k || k <= 0 || nums.empty()) return {};

        vector<int> res;
        int start = 0, end = k - 1;
        while(end < nums.size()){
            int temp = nums[start];
            for(int i = start+1; i <= end; ++i){
                temp = max(temp, nums[i]);
            }
            res.push_back(temp);
            ++start;
            ++end;
        }
        return res;
    }
};

方法二:使用双端队列 deque

  1. 动态维护一个双端队列,让队列的头部 front() 始终是当前滑动窗口的最大值的索引。
  2. 这样每次只需要取该索引对应的值,就是当前滑动窗口的最大值了。
  3. 注意:双端队列中储存的是元素的索引,而不是元素的值。因为当前滑动窗口已经离开队列的头部索引所指向的值时,我们需要弹出当前队列的头部索引。而只有让队列储存的是索引才能办到这一点。
  4. 之所以用双端队列,是因为容器的头部和尾部都会弹出元素,所以需要一个两端开口的容器。

代码实现:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size() < k || k <= 0 || nums.empty()) return {};

        vector<int> res;
        deque<int> findMax;

        for (int i = 0; i < nums.size(); ++i) {
            // i 每加一次,代表滑动窗口向右移一个单位。
            // i 指向的是每个滑动窗口的尾部元素(从 i = k - 1 开始)。

            if (i >= k && !findMax.empty()) {
                // i >= k 是为了确保 findMax.front() 至少为第一个完整的滑动窗口的最大值索引。
                // 即至少形成了一个完整的滑动窗口。
                res.push_back(nums[findMax.front()]);
            }

            while (!findMax.empty() && nums[i] >= nums[findMax.back()]) {
                // 如果新进来的 nums[i] 大于等于滑动窗口的尾部元素,
                // 说明该尾部元素肯定不会是任何滑动窗口的最大元素。
                // 就想象公司新来了一个既比你年轻又比你能干(大于等于你)的人,那你就只能被淘汰了,
                // 而且是循环淘汰掉所有不如新员工的老员工。
                findMax.pop_back();
            }

            if (!findMax.empty() && i - findMax.front() >= k) {
                // 虽然 findMax 的头部是最大的元素的索引,但是如果当前滑动窗口已不包括该索引,
                // 那么需要弹出该索引。
                // 就想象即使是公司的骨干成员,但是过了35岁也要被淘汰。
                findMax.pop_front();
            }

            findMax.push_back(i);
        }

        res.push_back(nums[findMax.front()]); // 最后一个滑动窗口还没计算就退出了 for 循环,需补上。

        return res;
    }
};

如有帮助到您,可以多多点赞、评论鼓励哟~~~