Skip to content

Latest commit

 

History

History
129 lines (84 loc) · 4.62 KB

面试题59_1_滑动窗口的最大值.md

File metadata and controls

129 lines (84 loc) · 4.62 KB

面试题 59_1 :滑动窗口的最大值


leetcode-cn 题目地址

📗difficulty: Medium

🎯Tags:


1. 题目描述📃

给定一个数组 nums 和滑动窗口的大小 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

提示:

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

注意:本题与主站 239 题相同:https://leetcode-cn.com/problems/sliding-window-maximum/


2. 解题思路💡

以下题解来自于 leetcode-cn 用户 Krahets 的题解,感谢他的持续精彩分析。

双端队列 实现 “单调队列”

一个大小为 K 的滑动窗口,向右滑动,前面的元素滑出,后面的元素滑入。双端队列的特征已经出来了。

本题需要求解的是窗口中的最大值,如果是对每个窗口求一次最大值,在没有特殊处理队列的情况下,整个复杂度会达到 O(n * K) 级别,如果能够在 O(1) 内完成获取最大值的工作,那么可以将复杂度下降到 O(n)

如果队列是“单调的”,只要保证队列头部 (or 尾部的值)为最大值,取出改元素即可。

有点类似与 剑指 Offer 30. 包含min函数的栈 ,需要对当前元素是否在队列中进行一个判断,如果改元素以及被滑出 滑动窗口了,相应地要从单调队列 中出队。

详细图解

可以有 2 个循环,分为 滑动窗口形成前,和滑动窗口形成后。当一个滑动窗口形成了,此时,就产生了第一个需要加入最终 res[] 数组的元素,其位置在 deque.peekFirst()

代码实现

public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums == null || nums.length == 0 || k <= 0) {
        return new int[0];
    }

    int[] maxInWindows = new int[nums.length - k + 1];
    // slideWindow 存储的是 非严格递减 的序列
    Deque<Integer> slideWindow = new LinkedList<>();

    // 初步建立滑动窗口
    for (int i = 0; i < k; i++) {
        // slideWindow 不为空 且 窗口最右边的数字 nums[i] 比它前面的数字都要大的话,就依次从队尾删除元素
        // 保证了队列的头部元素最大
        while (!slideWindow.isEmpty() && nums[i] > slideWindow.peekLast()) {
            slideWindow.pollLast();
        }
        // 将 nums[i] 存入队列中
        // 此时,队列 slideWindow 中元素是非严格递减的
        // 队列头部元素值最大
        slideWindow.offerLast(nums[i]);
    }
    // 大小为 k 的滑动窗口建立完成,继续处理
    // 窗口的最大值存入最终的数组
    maxInWindows[0] = slideWindow.peekFirst();

    for (int i = k; i < nums.length; i++) {
        // 如果队列头部的数字已经从其中滑出,那么滑出的数字也需要从队列的头部删除
        // 例如 nums.length = 8,k = 3 的情况。for 循环第一轮,如果当前最大值为 nums[0] 需要删除它,因为已经滑过去了
        if (!slideWindow.isEmpty() && slideWindow.peekFirst() == nums[i - k]) {
            slideWindow.pollFirst();
        }
        // 这里的判断是严格大于,如果不是严格判断,会造成数据误删除
        // 因为上面对元素是否出队进行了判断
        while (!slideWindow.isEmpty() && nums[i] > slideWindow.peekLast()) {
            slideWindow.pollLast();
        }
        slideWindow.offerLast(nums[i]);
        maxInWindows[i - k + 1] = slideWindow.peekFirst();
    }
    return maxInWindows;
}

复杂度分析

  • 时间复杂度:O(n)nums 数组每个元素访问一次。
  • 空间复杂度:O(k) 。不考虑最终返回数组需要的空间。存储滑动数组最大需要 O(k) 级别的空间。

3. 总结🎯

相似题目

剑指 Offer 30. 包含min函数的栈