力扣困难算法:滑动窗口最大值

题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
image

这个题用暴力解决会超时,看了别人的做法又觉得复杂看不懂。我查找了网上的各种解法,使用双端队列是最简便的,然后我归纳了一个最通俗易懂的思路,看一遍就能理解。

首先你可能会有疑问,为什么本题使用双端队列最合适呢?

有两个原因:1. 这个题的滑动窗口符合队列先进先出的特性。 2. 双端队列 Deque 能灵活操作两端,而普通队列 Queue 只能够移除队头和添加队尾

核心思想:

  1. 使用一个双端队列来表示滑动窗口,逐个遍历窗口中的元素,把符合条件的元素加入到队列。条件在第 3 点。

  2. 加入元素的过程中要保证,从队头到队尾的元素大小是单调递减的。最后队头元素就是该窗口的最大值。

  3. 要保证每个窗口的队列是单调递减的,所以新元素入队时要比较队尾大小,如果比队尾大,就删除该队尾直到遇见比新元素大的成员,然后新元素加入成为队尾。队列成员都比新元素小的话,就移除了所有队列成员,新元素就成为新的队头。

  4. 每次遍历新窗口时,要清理超出当前窗口的旧元素。

详细的解题步骤我放到代码注释里了,结合代码来看会更直观,里面也有一些细节要注意!并且代码的先后顺序不能反,核心步骤是4步。

我的Java代码:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
       
        Deque<Integer> deque = new ArrayDeque<>(); // 队列存储索引。为什么是索引呢?因为要清除超出窗口的元素时,只能根据索引来判断
        List<Integer> result = new ArrayList<>(); // 这里使用列表来存储结果,最后要把列表转为数组。你也可以直接定义数组来存储。
        int right = 0;  // 只需要右指针

        // 先处理异常情况
        if( k==0 || nums.length==0 || nums==null ){
            return new int[]{};
        }

        // 指针从第一个元素逐个右移。走完一个窗口后,开启新窗口
        for(int i=0; i<nums.length; i++){
            // 1. 清除队列里面不属于当前窗口的元素(就是判断队头元素)
            while(!deque.isEmpty() && deque.peek() < i-k+1){
                deque.pollFirst();
            }
            
            // 2. 让队列保持单调递减: 比较队尾与新元素,移除队列中所有小于新元素的成员,保证队头对应的元素最大
            while(!deque.isEmpty() && nums[i]>nums[deque.peekLast()]){
                deque.pollLast();
            }
            
            // 3. 让新元素入队
            deque.offer(i);
            
            // 4. 把当前窗口的最大元素加入到结果列表
            if(i >= k-1){   // 细节:必须走完第一个窗口的元素,才能记录最大元素
                result.add(nums[deque.peek()]); 
            }
        }

        // 最后,把结果列表转换为数组返回
        int[] resultArray = new int[result.size()];
        for(int i=0; i<result.size(); i++){
            resultArray[i] = result.get(i);
        }
        return resultArray;
    }
}

双端队列的一些添加元素和删除元素的不同方法比较:

添加元素的方法:

addFirst(E e)
将指定的元素插入到此双端队列的前端。
如果双端队列已满(容量有限制时),会抛出异常。

addLast(E e)
将指定的元素插入到此双端队列的尾端。
如果双端队列已满(容量有限制时),会抛出异常。

offerFirst(E e)
将指定的元素插入到此双端队列的前端,如果队列已满(容量有限制时)会返回 false,而不是抛出异常。

offerLast(E e)
将指定的元素插入到此双端队列的尾端,如果队列已满(容量有限制时)会返回 false,而不是抛出异常。

清除元素的方法:

removeFirst()
移除并返回此双端队列的第一个(前端)元素。
如果队列为空,会抛出异常。

removeLast()
移除并返回此双端队列的最后一个(尾端)元素。
如果队列为空,会抛出异常。

pollFirst()
检索并移除此双端队列的第一个(前端)元素,如果队列为空,返回 null,而不是抛出异常。

pollLast()
检索并移除此双端队列的最后一个(尾端)元素,如果队列为空,返回 null,而不是抛出异常。

clear()
移除双端队列中的所有元素。

总结
添加元素:
addFirst 和 addLast 会抛出异常(如果队列已满)。
offerFirst 和 offerLast 会返回 false(如果队列已满)。

清除元素:
removeFirst 和 removeLast 会抛出异常(如果队列为空)。
pollFirst 和 pollLast 会返回 null(如果队列为空)。
clear 用于清空整个队列。

posted @ 2025-07-20 23:08  junjunyi  阅读(34)  评论(0)    收藏  举报