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

相关推荐
liliangcsdn1 小时前
A3C算法从目标函数到梯度策略的探索
算法
陈天伟教授2 小时前
人工智能应用- 材料微观:06.GAN 三维重构
人工智能·神经网络·算法·机器学习·重构·推荐算法
liliangcsdn3 小时前
A3C强化学习算法的探索和学习
算法
Figo_Cheung3 小时前
Figo《量子几何学:从希尔伯特空间到全息时空的统一理论体系》(二)
算法·机器学习·几何学·量子计算
额,不知道写啥。3 小时前
HAO的线段树(中(上))
数据结构·c++·算法
LYS_06183 小时前
C++学习(5)(函数 指针 引用)
java·c++·算法
紫陌涵光3 小时前
669. 修剪二叉搜索树
算法·leetcode
NGC_66114 小时前
二分查找算法
java·javascript·算法