zoukankan      html  css  js  c++  java
  • 剑指Offer_#59-I_滑动窗口的最大值

    剑指Offer_#59-I_滑动窗口的最大值

    Contents

    题目

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

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

    思路分析

    用一个双端队列维护窗口的最大值,保证双端队列是非严格递减的(前一个元素大于等于后一个元素),那么双端队列的第一个元素正好就是当前窗口当中的最大值。
    算法流程
    遍历数组,维护两个指针i,j,分别指向窗口左右边界,右指针从第一个元素开始,左指针则始终与右指针相距k-1(中间刚好有k个元素),指针每次移动后执行如下过程:

    1. i>0时(在此之前窗口还未形成),如果当前队列的头部恰好是nums[i-1],则将其从队列里删除
    2. 比较nums[j]与队列当中的数字(从后往前看),将小于nums[j]的全部删除(因为nums[j]是当前新加入的数字,比它小的绝不可能是窗口最大值)
    3. 将nums[j]加入到队列尾部,这时整个队列是非严格递减的,头部的值就是当前窗口的最大值
    4. 如果i >= 0,将头部数字加入到res中

    总结起来,就是维护一个特殊的双端队列数据结构,窗口滑动时,将上个窗口的第一个元素删除,再把上个窗口之后的第一个元素加入进来,且保证队列头部数字最大。

    细节问题:

    1. 指针初始值:j = 0,i = 0-(k - 1) = 1-k
    2. 结果数组res的长度:从第k个元素开始,到第n个元素,有几个数字?n-k+1
    3. java的双端队列:Deque<Integer> deque = new LinkedList<>();
      • peekFirst(),peekLast()
      • removeFirst(),removeLast()
      • addLast
    4. 特殊情况:空数组,k = 0,返回空数组
    5. 删除元素的时候,必须保证队列非空

    解答

    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            int n = nums.length;
            if(n == 0 || k == 0) return new int[0];
            Deque<Integer> deque = new LinkedList<>();
            //res的长度就是滑动窗口的个数,共有n-k+1个
            int[] res = new int[n - k + 1];
            //ERROR:不可以写成 int i = 1 - k,int j = 0; 因为这样写是两个statement
            for(int i = 1 - k,j = 0; j <= n - 1; i++, j++){
                //1.如果当前窗口删除掉的nums[i - 1]恰好时上一个窗口的最大值,在队列中将这个元素删除
                if(i >= 1 && deque.peekFirst() == nums[i - 1])
                    deque.removeFirst();
                //2.当前窗口新加入nums[j],小于此值的绝对不会是最大值,所以直接从队列将这些较小的数字删除(从后往前)
                //ERROR:这里判断非空要写在前,利用短路特性
                while(!deque.isEmpty() && nums[j] > deque.peekLast())
                    deque.removeLast();
                //3.将新加入的nums[j]加入队列末尾
                deque.addLast(nums[j]);
                //4.当前窗口最大值就是队列头部,将其写入res
                if(i >= 0) res[i] = deque.peekFirst();
            }
            return res;
        }
    }

    复杂度分析

    时间复杂度:O(n),遍历整个数组
    空间复杂度:O(k),因为双端队列里边的数字始终就是当前窗口的数字,所以队列的大小是k

  • 相关阅读:
    Quartz.net任务调度
    基于Task定时检测网络本地网络状况
    java基于NIO的分散读取文件,然后统一聚合后写入文件
    基于直接缓冲区和非直接缓冲区的javaIO文件操作
    java 读取xml
    WPF 通过线程使用ProcessBar
    NPOI自适应列宽
    C#序列化与反序列化以及深拷贝浅拷贝
    Springboot feign 传递request信息
    apereo cas 小记01--服务器搭建01
  • 原文地址:https://www.cnblogs.com/Howfars/p/13384975.html
Copyright © 2011-2022 走看看