算法题解记录--239滑动窗口最大值

239. 滑动窗口最大值 - 解题记录

📌 题目描述

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

示例 1:

复制代码
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]

示例 2:

复制代码
输入:nums = [1], k = 1
输出:[1]

🧠 思路演进过程

思路一:暴力解法(最容易想到)

每次窗口移动后,遍历窗口内的 k 个元素,找出最大值。

java 复制代码
for (int i = 0; i <= nums.length - k; i++) {
    int max = nums[i];
    for (int j = i; j < i + k; j++) {
        max = Math.max(max, nums[j]);
    }
    result[i] = max;
}

时间复杂度: O(n × k)
问题: 数据量大时会超时 ❌


思路二:优化思路(记录最大值)

每次滑动窗口,只引入一个新元素,丢弃一个旧元素:

  • 如果新元素 ≥ 当前最大值 → 新元素成为最大值
  • 如果新元素 < 当前最大值:
    • 如果丢弃的不是当前最大值 → 最大值不变
    • 如果丢弃的是当前最大值 → 重新遍历窗口找最大值

问题: 数组递减时,每次都要重新遍历,复杂度仍为 O(n × k)


思路三:单调队列(最优解)✨

使用双端队列(Deque)维护一个单调递减 的队列,存储的是数组元素的下标

核心思想:

  • 队首永远是当前窗口的最大值
  • 保持队列中下标对应的元素值从大到小
  • 移除过期元素(不在当前窗口)
  • 移除不可能成为最大值的元素(比新元素小的)

🏗️ 单调队列详解

队列特性

特性 说明
存储内容 数组元素的下标
队首 → 队尾 下标对应元素值单调递减
队首作用 当前窗口最大值的下标
队尾作用 插入新元素的位置

操作规则

  1. 移除过期元素:如果队首下标 ≤ i - k,说明已离开窗口,弹出队首
  2. 保持单调性:从队尾开始,移除所有比新元素小的元素(它们再也没机会成为最大值)
  3. 插入新元素:将新元素下标加入队尾
  4. 收集结果:当 i ≥ k-1 时,队首下标对应的元素就是当前窗口的最大值

🎬 动图演示

nums = [1,3,-1,-3,5,3,6,7], k = 3 为例:

步骤 窗口 队列(存下标) 队列对应值 最大值
i=0 1 0 1 -
i=1 1,3 1 3 -
i=2 1,3,-1 1,2 3,-1 3
i=3 3,-1,-3 1,2,3 3,-1,-3 3
i=4 -1,-3,5 4 5 5
i=5 -3,5,3 4,5 5,3 5
i=6 5,3,6 6 6 6
i=7 3,6,7 7 7 7

💻 代码实现

java 复制代码
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0) return new int[0];
        
        int n = nums.length;
        int[] result = new int[n - k + 1];
        Deque<Integer> deque = new ArrayDeque<>(); // 存储下标
        
        for (int i = 0; i < n; i++) {
            // 1. 移除不在窗口内的元素(从队首)
            if (!deque.isEmpty() && deque.peekFirst() <= i - k) {
                deque.pollFirst();
            }
            
            // 2. 保持单调递减:移除所有比新元素小的元素(从队尾)
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
            
            // 3. 加入新元素
            deque.offerLast(i);
            
            // 4. 收集结果(从第 k-1 个元素开始)
            if (i >= k - 1) {
                result[i - k + 1] = nums[deque.peekFirst()];
            }
        }
        
        return result;
    }
}

⚙️ 复杂度分析

复杂度 说明
时间复杂度 O(n) - 每个元素最多入队出队一次
空间复杂度 O(k) - 队列最多存储 k 个元素

📝 关键点总结

  1. 为什么存下标不存值?

    • 需要判断元素是否还在窗口内(通过下标和 i-k 比较)
    • 通过下标可以快速获取元素值
  2. 为什么用双端队列?

    • 需要从队首移除过期元素
    • 需要从队尾移除比新元素小的元素
    • 需要从队尾添加新元素
  3. 单调队列的本质

    • 维护一个"候选最大值"的序列
    • 新元素会"干掉"所有比它小的旧元素
    • 队首永远是当前最强选手

🎯 举一反三

这种单调队列的思想还可以解决:

  • 滑动窗口最小值
  • 滑动窗口最值问题变种
  • 需要维护一个动态集合的最值问题

📚 参考资料

  • Java Deque 接口:ArrayDeque 是常用实现类
  • 关键方法:
    • peekFirst() / peekLast() - 查看队首/队尾
    • pollFirst() / pollLast() - 移除并返回队首/队尾
    • offerFirst() / offerLast() - 添加元素到队首/队尾

💡 记住:单调队列就是用空间换时间,用 O(n) 的代价解决原本 O(n×k) 的问题!

相关推荐
小欣加油6 小时前
leetcode56 合并区间
c++·算法·leetcode·职场和发展
lqqjuly6 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
徐小夕7 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei7 小时前
2026年高考数学各省真题及答案(完整版)
算法·高考
Hello:CodeWorld8 小时前
C 风格变参 vs C++ 变参模板:核心区别与选型指南
c语言·c++·算法
8Qi89 小时前
LeetCode 516:最长回文子序列
算法·leetcode·职场和发展·动态规划
youngerwang10 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
想要成为糕糕手11 小时前
前端必修课:JavaScript 数组与数据结构底层逻辑全解析
javascript·数据结构·面试
KaMeidebaby11 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
手写码匠12 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc