美文网首页
滑动窗口最大值

滑动窗口最大值

作者: lkzy | 来源:发表于2020-03-15 22:37 被阅读0次

    滑动窗口最大值

    描述

    给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。
    你只可以看到在滑动窗口内的 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

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/sliding-window-maximum

    题解

    暴力破解

    思路
    遍历每个滑动窗口,在每个滑动窗口中获取最大值;
    代码

      public int[] maxSlidingWindow(int[] nums, int k) {
            int n = nums.length;
            if (n * k == 0) return new int[0];
    
            int [] output = new int[n - k + 1];
            for (int i = 0; i < n - k + 1; i++) {
                int max = Integer.MIN_VALUE;
                for(int j = i; j < i + k; j++)
                    max = Math.max(max, nums[j]);
                output[i] = max;
            }
            return output;
        }
    

    复杂度分析
    时间复杂度: 在nums数组中遍历获得滑动窗口O(N),在滑动窗口中获取最大值的时间复杂度为O(k),
    因为每一次遍历数组获取滑动窗口都需获取滑动窗口的最大值,
    因此最终的时间复杂度为O(N*k);
    空间复杂度:返回值所需O(N-l+1);

    优先队列

    思路
    使用大顶堆来维护滑动窗口中的的数据,大顶堆的堆顶元素即为滑动窗口中最大值。
    算法步骤:

    1. 准备:
    • 源数据nums数组,长度为N
    • 滑动窗口大小k
    • 优先队列(大顶堆)priorityQueue,大小需为k
    • 结果数组results,长度为N-k+1
    1. 初始化priorityQueue:由于滑动窗口是从nums的下标k-1开始遍历的,因此先
      使用nums中[0,k-1]上的值来初始化priorityQueue;
      nums[0,k-1]为第一个滑动窗口,因此results[0]为此时priorityQueue的最优先元素;
    2. 遍历nums:从k到N遍历nums,下标为i,此时滑动窗口范围为nums[i-k+1,i],priorityQueue
      的值为nums[i-k,i-1];
    3. 调整优先队列:将nums[i]添加到priorityQueue,nums[i-k],从priorityQueue移除,
      保持priorityQueue的值与滑动窗口的值一致;
    4. 添加滑动窗口最大值:优先队列的队首元素为滑动窗口此时的最大值,
      results[i-k+1]=priorityQueue的队首元素;
      代码
    public class PriorityQueueSolution {
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
               return o2-o1;
            }
        };
    
        public int[] maxSlidingWindow(int[] nums, int k) {
            int len = nums.length;
            if (k > len || k < 1) {
                return new int[0];
            }
            int[] result = new int[len - k + 1];
            PriorityQueue<Integer> queue = new PriorityQueue<>(k, comparator);
            //初始化优先队列
            int i = 0;
            while (i < k - 1) {
                queue.add(nums[i]);
                i++;
            }
            //从滑动窗口中获取最大值
            for (int right = k - 1; right < len; right++) {
                queue.add(nums[right]);
                if (queue.size() > k) {
                    queue.remove(nums[right-k]);
                }
                result[right - k + 1] = queue.element();
            }
            return result;
        }
    }
    

    复杂度分析
    时间复杂度: 在nums数组中遍历获得滑动窗口O(N),优先队列删除、添加元素的复杂度为O(logk),
    获取优先队列的队首元素的时间复杂度为O(1),
    因此最终的时间复杂度为O(N*logk);
    空间复杂度:返回值所需O(N-l+1),优先队列为O(k),最终为O(N);

    双端队列

    思路
    使用双端队列来存储滑动窗口在nums中对应的下标;
    双端队列限制:

    • 靠近尾部的队列元素的值需要比靠近队首的元素的值要大,
      即当窗口滑动时,滑动窗口中增加的元素在nums的下标是从队尾添加的,
      滑动窗口移除的元素在nums中的下标是从队首移除的;
    • 靠近尾部的队列元素的值作为下标在nums值需要比靠近队首的要小,
      即当队列添加下标之前要从队列队尾去除这些下标(它们在nums中对应的值要比要添加
      下标在nums中的要小);
      经过上述限制,队列的队首下标在nums中对应的值始终是队列的最大值,即为滑动窗口中的最大值;
      代码
    public class DequeSolution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            int len = nums.length;
            if (k > len || k < 1) {
                return new int[0];
            }
            int[] result = new int[len - k + 1];
            //用于存储在滑动窗口中有效的索引,且双端队列会从前向后维护一个降序的排列,
            // 获取滑动窗口中的最大值,只需要获取双端队列的首值即可
            Deque<Integer> indexDeque = new ArrayDeque<>(k);
            for (int i = 0; i < len; i++) {
                addAndRemove(nums, indexDeque, i, k);
                if (i >= k - 1) {
                    result[i - k + 1] = nums[indexDeque.getFirst()];
                }
            }
            return result;
        }
    
        private void addAndRemove(int[] nums, Deque<Integer> indexDeque, int index, int k) {
            //如果indexDeque中的开始元素的索引在当前滑动窗口的范围之外删除
            if (indexDeque != null && indexDeque.size() > 0 && (index - indexDeque.getFirst()) > k - 1) {
                indexDeque.removeFirst();
            }
            //循环从indexDeque后部取出索引indexLast,与要添加的索引index,将它们在nums中的值比较,若值indexLast的值比较小,
            // 则将indexLast从indexDeque删除,这样可以保证indexDeque中的索引在nums中的取值是降序排列,也能保证indexDeque中的
            // 首个值是滑动窗口的最大值的索引
            while (indexDeque != null && indexDeque.size() > 0 && nums[index] > nums[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(index);
        }
    }
    

    复杂度分析
    时间复杂度: 在nums数组中遍历获得滑动窗口O(N),双端队列删除、获取队首元素的复杂度为O(1);添加元素的复杂度为O(1)~O(logk),
    因此最终的时间复杂度为O(N)~O(log(k));
    空间复杂度:返回值所需O(N-l+1),双端队列为O(k),最终为O(N);

    动态规划

    思路
    构造left,right数组;

    • 将nums按k的大小分成多段,最后一段可能不到k个元素;
    • left数组中,left中按分段与nums对应,left分段中从左到右对应索引的值,是nums对应分段中对应索引及之前的索引的之中最大的一个值
    • right数组中,right中按分段与nums对应,right分段中从右到左对应索引的值,是nums对应分段中对应索引及之前的索引的之中最大的一个值
    • 因此获取一个滑动窗口的最大值,是max(滑动窗口最左边位置的索引在right数组中的值,滑动窗口最右边位置的索引在left数组中的值

    代码

    public class DynamicProgrammingSolution implements ISolution {
        @Override
        public int[] maxSlidingWindow(int[] nums, int k) {
            int len = nums.length;
            if (k > len || k < 1) {
                return new int[0];
            }
            int[] left=new int[len];
            int[] right=new int[len];
            //构造left、right数组
           for(int i=0;i<len;i++){
                //left构造
                if(i%k==0){
                    left[i]=nums[i];
                }else{
                    left[i]=Math.max(left[i-1],nums[i]);
                }
    
                //right构造,从nums数组右边开始遍历
                int index=len-i-1;
                if(index==len-1||(index+1)%k==0){
                    right[index]=nums[index];
                }else{
                    right[index]=Math.max(right[index+1],nums[index]);
                }
            }
    
            //获取滑动窗口最大值数组
            int[] result=new int[len-k+1];
            for(int i=0;i<len-k+1;i++){
                result[i]=Math.max(right[i],left[i+k-1]);
            }
            return result;
        }
    }
    

    复杂度分析
    时间复杂度: 构造left,rigt数组O(N),在nums数组中遍历获得滑动窗口O(N).
    因此最终的时间复杂度为O(2N);
    空间复杂度:返回值所需O(N-l+1),left,right数组分别为O(N),最终为O(3
    N);

    相关文章

      网友评论

          本文标题:滑动窗口最大值

          本文链接:https://www.haomeiwen.com/subject/yveiehtx.html