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

相关推荐
Dlrb12111 天前
C语言-指针三
c语言·算法·指针·const·命令行参数
Tisfy1 天前
LeetCode 2540.最小公共值:双指针(O(m+n))
算法·leetcode·题解·双指针
IronMurphy1 天前
【算法四十七】152. 乘积最大子数组
算法
淘矿人1 天前
Claude辅助DevOps实践
java·大数据·运维·人工智能·算法·bug·devops
Cosolar1 天前
万字详解:RAG 向量索引算法与向量数据库架构及实战
数据库·人工智能·算法·数据库架构·milvus
小江的记录本1 天前
【Java基础】泛型:泛型擦除、通配符、上下界限定(附《思维导图》+《面试高频考点清单》)
java·数据结构·后端·mysql·spring·面试·职场和发展
落羽的落羽1 天前
【算法札记】练习 | Week4
linux·服务器·数据结构·c++·人工智能·算法·动态规划
萑澈1 天前
算法竞赛入门:C++ STL核心用法与时空复杂度速查手册
数据结构·c++·算法·stl
Godspeed Zhao1 天前
从零开始学AI16——SVM
算法·机器学习·支持向量机