每日一题:滑动窗口最大值

66 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

题目链接

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

解题思路:单调队列

在开始前,先复习下什么是双端数列

我们应该都学过简单的队列和栈吧

  1. 普通数列是限制仅在队尾可进行插入,在队头可进行删除操作的线性表,队列的插入叫做入队列,队列的删除叫做出队列
  2. 栈则是先进后出,后进先出,和队列正好相反

双端队列可以理解为是两种数据结构的合体,它同时具备了队列和栈的特性。即队列的队头既可以进行插入操作,也可以进行删除操作。队列的队尾也既可以进行插入操作,又可以进行删除操作。

而有些时候,双端队列还有受限的双端队列:即输入受限的双端队列,和输出受限的双端队列

  1. 输入受限的双端队列:允许在一段进行入队和出队,但在另一端只允许出队的双端队列。

  2. 输入受限的双端队列:允许在一段进行入队和出队,但在另一端只允许入队的双端队列。

而输出受限的双端队列里,又有一种情况,即队列里的各元素之间的关系具有单调性,这叫单调队列。

单调队列:所有队列里的元素都是按递增(递减) 的顺序队列,这个队列的头是最小(最大)的元素。

这道题,用到的就是双端队列中的单调队列

具体思路:

  1. 因为这是一个滑动窗口,我们需要不断的向右移动,所以窗口里的值是在不断变化的。向右移动的过程中左边的元素不断被删除,右边的元素不断添加进来,我们只需要在这个窗口里寻找最大值
  2. 那我们就可以只在队列中保留可能成为窗口最大元素的元素,去掉不可能成为窗口中最大元素的元素。想象一下,如果要进来的是个值大的元素,那一定会比之前早进去的值小的元素晚离开队列,而且值大的元素在,都没值小的元素啥事,所以值小的元素直接弹出队列即可。
  3. 遍历给定数组中的元素,如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。直到,队列为空或当前考察元素小于新的队尾元素;
  4. 当队首元素的下标小于滑动窗口左侧边界left时,表示队首元素已经不再滑动窗口内,因此将其从队首移除。
  5. 由于数组下标从0开始,因此当窗口右边界right+1大于等于窗口大小k时,意味着窗口形成。此时,队首元素就是该窗口内的最大值。

具体代码:(JAVA实现)

public static int[] maxSlidingWindow(String[] nums, int k) {
    int[] ints = new int[nums.length - k + 1];
    LinkedList<Integer> queue = new LinkedList<>();

    for (int i = 0; i < nums.length; i++) {

        /**
         * 保证数列都是从大到小顺序,如果队列前面数字较小,依次弹出,直到队列头部元素为最大值
         */
        while (!queue.isEmpty() && Integer.valueOf(nums[queue.peekLast()]) <= Integer.valueOf(nums[i])) {
            queue.removeLast();
        }
        /**
         * 添加当下值对应的下标
         */
        queue.offer(i);
        /**
         * 如果当前队列的最左端存的下标等于 i - k 的值,就代表队列满了
         * 但是新元素需要进来,所以队列最左端退队列
         */
        if (queue.peek() <= i - k) {
            queue.poll();
        }


        /**
         * 当前的大值加入到结果数组中
         */
        if (i >= k - 1) {
            ints[i + 1 - k] = Integer.valueOf(nums[queue.peek()]);
        }
    }
    return ints;
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(k),k为辅助队列的长度

提交结果

image.png