每日一题

题目信息

  • 时间: 2019-07-05

  • 题目链接:Leetcode

  • tag: 队列 双端队列 滑动窗口

  • 难易程度:困难

  • 题目描述:

    给定一个数组 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

注意

1. 你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

解题思路

本题难点

数组无序,窗口内的最大值。

具体思路

双向队列:在头部进行插入、删除操作,也能在尾部进行插入删除操作。

单调的双向队列:从队列的头部到尾部,所存储的元素是依次递减(或依次递增)的。

我们维护一个单调的双向队列,窗口在每次滑动的时候,我就从队列头部取当前窗口中的最大值,每次窗口新进来一个元素的时候,我就将它与队列中的元素进行大小比较:

  • 进来的元素 > 队列的尾部元素:那么先将队列尾部的元素弹出,然后把刚刚进来的元素添到队列的尾部;
  • 进来的元素 < 队列的尾部元素:那么把刚刚进来的元素直接添到队列的尾部即可。

删除元素:当队列的大小超过窗口的大小时,将队列的最大值弹出,窗口进行滑动。

代码

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || k == 0){
            return new int[0];
        }
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        int count = 0;
        for(int i = 0; i < nums.length; i++){
            // 在队列不为空的情况下,如果队列尾部的元素要比当前的元素小,或等于当前的元素
            // 那么为了维持从大到小的原则,我必须让尾部元素弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]){
                deque.pollLast();
            }
           // 不走 while 的话,说明我们正常在队列尾部添加元素
            deque.add(i);
          // 如果滑动窗口已经略过了队列中头部的元素,则将头部元素弹出
            if(deque.peekFirst() == (i - k)){
                deque.pollFirst();
            }
          // 看看窗口有没有形成,只有形成了大小为 k 的窗口,我才能收集窗口内的最大值
            if(i >= k-1){
                res[count++] = nums[deque.peekFirst()];
            }
        }
        return res;
    }
}

复杂度分析:

  • 时间复杂度 O(N) : 其中 n 为数组 nums 长度;线性遍历 nums 占用 O(N) ;每个元素最多仅入队和出队一次,因此单调队列 deque 占用 O(2N) 。
  • 空间复杂度 O(k) : 双端队列 deque 中最多同时存储 k个元素(即窗口大小)。

其他优秀解答

解题思路

改善之后的暴力法

代码

 public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length;
        if (len == 0){
            return new int[0];
        }
        //定义结果数组
        int[] res = new int[len - k + 1];
        //maxInd记录每次最大值的下标,max记录最大值
        int maxInd = -1, max = Integer.MIN_VALUE;

        for (int i = 0; i < len - k + 1; i++) {
            //判断最大值下标是否在滑动窗口的范围内
            if (maxInd >= i){
                //存在就只需要比较最后面的值是否大于上一个窗口最大值
                if (nums[i + k - 1] > max){
                    max = nums[i + k - 1];
                    //更新最大值下标
                    maxInd = i + k - 1;
                }
            }
            //如果不在就重新寻找当前窗口最大值
            else {
                max = nums[i];
                for (int j = i; j < i + k; j++) {
                    if (max < nums[j]) {
                        max = nums[j];
                        maxInd = j;
                    }
                }
            }
            res[i] = max;
        }
        return res;
    }
原文地址:https://www.cnblogs.com/ID-Wangqiang/p/13273074.html