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

分享一下刷题记录

滑动窗口的最大值

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;
    }
}
相关推荐
数模竞赛Paid answer38 分钟前
2026年华东杯数学建模B题医药物流安排问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
不会代码的小猴1 小时前
21. 泛型编程上
开发语言·c++·笔记·算法
AI备案指南-满满2 小时前
大模型与算法备案全流程详解:从零到通过的完整指南
人工智能·算法·备案·大模型备案·算法备案
江畔柳前堤2 小时前
LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系
网络·人工智能·深度学习·算法·机器学习·语音识别
一只旭宝2 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
良木林3 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展
陌诺曦.3 小时前
Python扩展小练习
算法
coder!mq4 小时前
说几个常见的语法糖?
java·开发语言·算法
不爱学英文的码字机器5 小时前
推荐算法梳理,六种主流模型与九步训练流程
算法·机器学习·推荐算法