• 滑动窗口最大值(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    示例 2:

    输入:nums = [1], k = 1
    输出:[1]
    
    • 1
    • 2

    提示:

    • 1 <= nums.length <= 10^5
    • -10⁴ <= nums[i] <= 104⁴
    • 1 <= k <= nums.length

    解析

    暴力破解

    这道题如果用暴力破解的方式比较简单,但是时间复杂度比较高,先说暴力破解的方法。

    窗口内的数字数量是已知的k,可以根据原始数组nums跟窗口内数字数量k得出有多少个窗口,也就是返回的数组长度,公式为:nums.length - k + 1,以示例一代入公式:8 - 3 + 1 = 6 示例一的窗口数量为6个,后面的循环次数用窗口数量即可。

    在这里插入图片描述

    窗口数量已知可能性就已知,所以第一层循环次数直接以nums.length - k + 1结果的值为判定条件,二层循环则去判定窗口内k个数值的最大值,取每个窗口的最大值写入到返回数组,这是暴力破解的方式。

    代码如下:

    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            int fnum = nums.length-k+1;
            int result[] = new int[fnum];
            for(int i = 0; i < fnum; i++){
                int max = -10001;
                for(int j = i;j < k+i; j++){
                    if(nums[j] > max){
                        max = nums[j];
                    }
                }
                result[i] = max;
            }
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行结果:

    在这里插入图片描述

    暴力破解的时间复杂度为O(n²)空间复杂度O(1),这种方式的解法超出了时间限制,那就得换一种方式了。

    队列最大值

    既然题目要求的是获取每个窗口的最大值,在循环的时候使用队列记录窗口循环下标内最大的值,在队列中保证队列是有序的递减队列,这样获取最大值直接从队列头获取即可,如果队列尾值 < 循环值需要将队列尾值弹出,如果循环下标超出窗口位数要将队列头值弹出。

    在这里插入图片描述
    操作步骤:

    输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
    输出: [3,3,5,5,6,7]
    
    L-R为窗口值
    初始状态:L=R=0,队列:{}
    i=0,nums[0]=1。队列为空,直接加入。队列:{1}
    i=1,nums[1]=3。队尾值为13>1,弹出队尾值,加入3。队列:{3}
    i=2,nums[2]=-1。队尾值为3-1<3,直接加入。队列:{3,-1}。此时窗口已经形成,L=0,R=2,result=[3]
    i=3,nums[3]=-3。队尾值为-1-3<-1,直接加入。队列:{3,-1,-3}。队首3对应的下标为1,L=1,R=3,有效。result=[3,3]
    i=4,nums[4]=5。队尾值为-35>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
    i=5,nums[5]=3。队尾值为53<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
    i=6,nums[6]=6。队尾值为36>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
    i=7,nums[7]=7。队尾值为67>6,弹出队尾值后加入。队列:{7}。此时L=5,R=7,有效。result=[3,3,5,5,6,7]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码如下:

        public int[] maxSlidingWindow(int[] nums, int k) {
            ArrayDeque<Integer> deque = new ArrayDeque<Integer>();
            int result[] = new int[nums.length-k+1];
            for (int i = 0; i < nums.length; i++) {
                // 判断值是否比尾部值大,大的话将队列的尾部值退出
                while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
                    deque.pollLast();
                }
                deque.addLast(i);
                // 超出窗口的值退出队列
                if(deque.peek() <= i-k){
                    deque.poll();
                }
                // 前面n-1不参与数组写入
                if(i+1 >= k){
                    result[i+1-k] = nums[deque.peek()];
                }
            }
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    执行结果:
    在这里插入图片描述

    这种方法的时间复杂度为O(n)

  • 相关阅读:
    【JavaWeb】Filter
    基于SSM的社区疫情居民信息登记系统
    理解在Unity中使用多个相机
    pytorch深度学习实战lesson32
    Spring Boot与Web开发-快速开发CRUD
    Leetcode每日刷题之 283. 移动零(C++)
    ChartJS使用-环境搭建(vue)
    AI赋能的3D资产管理
    【计算机网络】运输层:单方向传输报文管道利用的情况
    Java 类之 java.lang.reflect.Method
  • 原文地址:https://blog.csdn.net/AnNanDu/article/details/126851426