算法题解记录--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) 的问题!

相关推荐
white-persist7 小时前
【vulhub weblogic CVE-2017-10271漏洞复现】vulhub weblogic CVE-2017-10271漏洞复现详细解析
java·运维·服务器·网络·数据库·算法·安全
汀、人工智能7 小时前
[特殊字符] 第9课:三数之和
数据结构·算法·数据库架构·图论·bfs·三数之和
汀、人工智能7 小时前
[特殊字符] 第10课:接雨水
数据结构·算法·数据库架构·图论·bfs·接雨水
辰痕~7 小时前
数据结构-第一节课
数据结构
故事和你918 小时前
蓝桥杯-2025年C++B组国赛
开发语言·软件测试·数据结构·c++·算法·职场和发展·蓝桥杯
py有趣8 小时前
力扣热门100题之合并区间
算法·leetcode
cpp_25018 小时前
P10108 [GESP202312 六级] 闯关游戏
数据结构·c++·算法·动态规划·题解·洛谷·gesp六级
Lzh编程小栈8 小时前
数据结构与算法之队列深度解析:循环队列+C 语言硬核实现 + 面试考点全梳理
c语言·开发语言·汇编·数据结构·后端·算法·面试
AbandonForce8 小时前
模拟实现vector
开发语言·c++·算法
少许极端8 小时前
算法奇妙屋(四十二)-贪心算法学习之路 9
学习·算法·贪心算法