[算法手记] 滑动窗口最大值

分享一下刷题记录

滑动窗口的最大值

1.题目链接

题目链接 : https://leetcode.cn/problems/sliding-window-maximum/description/?envType=study-plan-v2\&envId=top-100-liked

2. 题目分析

题目不难理解 ,让一个固定长度为k的窗口在数组中从左往右每次滑动一位, 返回每次窗口中元素的最大值

使用滑动窗口的思路来解决的话, 我们只需要维护一个最大值Max即可, 每次进窗口时更新把新加入的元素和Max进行比较,但是这道题之所以成为困难题就是出现在出窗口这个逻辑上

出窗口时, 窗口内元素一进一出, 这时候如何维护Max的值又成了一个问题,

  • 如果窗口left指针出窗口的元素不是最大值的话还好, 因为此时最大值还在窗口中,还处于有效的状态, 新入窗口的元素仍旧可以和它进行比较
  • 如果恰好出窗口的元素刚好就是原来维护的Max时, Max就变为无效元素了, 我们就需要找出在移动之后的窗口内的最大元素, 把它的值赋于Max, 如果使用暴力的解法的话, 时间复杂度则为O(k* n),在窗口长度过大, nums数组元素过多时都会导致超时
    下面附上暴力解法:
java 复制代码
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        List<Integer> list = new ArrayList<>();
        int n = nums.length, l = 0, r = 0;
        if (n == 1) return new int[]{nums[0]};
        
        int sum = Integer.MIN_VALUE;
        for (; r < n; r++) {
            sum = Math.max(sum, nums[r]);
            // 窗口满
            if (r - l + 1 == k) {
                list.add(sum); // 1. 先把当前窗口的最大值存起来

                // 2. 准备让 l 右移。在右移前,检查即将出窗口的 nums[l] 是否是最大值
                if (nums[l] == sum) {
                    // 如果是,我们需要在 [l+1, r] 这个范围内重新找一个最大值
                    sum = Integer.MIN_VALUE;
                    for (int i = l + 1; i <= r; i++) {
                        sum = Math.max(sum, nums[i]);
                    }
                }
                l++; // 3. 左边界正式右移
            }
        }
        return list.stream().mapToInt(Integer::intValue).toArray();
    }
}

最终也是不出所料的超出时间限制

使用单调队列就可以很好的解决这个问题,什么是单调队列呢

3. 单调队列

1. 核心特性

单调队列不仅仅是普通队列(FIFO),它允许从队尾进行元素的删除操作。其核心逻辑如下:

  • 队列性质 :队列中的元素值必须满足单调性(如:若要求最大值,则队列内元素从队头到队尾必须是严格单调递减的)。

  • 元素入队(维持单调性) :当一个新的元素 xxx 进入窗口时:

    1. 如果 xxx 大于队尾元素,那么队尾元素永远不可能成为当前或后续窗口的最大值,直接将其弹出。

    2. 重复上述操作,直到队尾元素大于 xxx 或队列为空,然后将 xxx 入队。

  • 元素出队(过期处理) :由于窗口大小限制为 KKK,如果队头元素已经滑出当前窗口范围(索引小于 当前索引 - K + 1),则将其弹出。

2. 为什么使用它?

  • 时间复杂度 :每个元素最多进队一次、出队一次,因此总时间复杂度为 O(N)O(N)O(N) 。相比于暴力求解的 O(N×K)O(N \times K)O(N×K),在大数据量下效率极高。

  • 空间复杂度 :O(K)O(K)O(K),最多存储 KKK 个元素。

3.为什么不使用大根堆?

本题的难点在于如何找出移动之后窗口内的最大值,最开始我试图使用大根堆来处理? 来维护一个元素个数为k的大根堆, 但是很快就发现是行不通的,虽然大根堆可以很方便的获取到窗口内的最大值, 但是堆内部的元素顺序和窗口内的元素顺序是不一致的, 这就导致了我们虽然在进窗口时会堆化把最大值置于堆顶,但是出窗口时却需要遍历整个堆来找到要出窗口的那个元素, 所以使用大根堆是行不通的

但是单调队列内部是排资论辈的, 新来的如果比队尾小, 就不会加入队列中, 如果比队尾大则弹出队尾元素取而代之, 这就保证了队尾元素一定是最新加入的元素, 而队首元素一定是最大,资历最老的元素,可以有效的维护窗口在滑动过程中的Max值

4.代码参考(单调队列)

java 复制代码
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if(n == 1) return new int []{nums[0]};
        int []ret = new int [n - k + 1];
        int retIndex = 0;
        
        //使用单调队列
        Deque<Integer> deque = new ArrayDeque<>();

        for(int r = 0;r < n;r++){
            //检查队头元素,排除最大值是否过期
            if(!deque.isEmpty() && deque.peekFirst() < r - k + 1){//队首最大值该出窗口了
                deque.pollFirst();
            }

            //新加入窗口的比队尾元素大
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[r]){
                deque.pollLast();//队尾弹出
            }
            //较大值的新元素加入队尾
            deque.offerLast(r);

            //窗口大小达到k时记录结果
            if(r >= k - 1){
                ret[retIndex++] = nums[deque.peekFirst()];
            }
        }
        return ret;
    }
}
相关推荐
HjhIron9 小时前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩11 小时前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹12 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术16 小时前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
浮生望18 小时前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰18 小时前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法
地平线开发者1 天前
J6B vio scenario sample
算法
BothSavage2 天前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn2 天前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法