(LeetCode-Hot100)239. 滑动窗口最大值

❌|✅|💡|📌 239. 滑动窗口最大值

🔗 问题简介

LeetCode 239. 滑动窗口最大值

复制代码
题解github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

📝 题目描述

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

返回 滑动窗口中的最大值


🧪 示例说明

示例 1:

复制代码
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

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

💡 解题思路

方法一:暴力法(❌ 超时)

  • 对每个窗口遍历 k 个元素找最大值。
  • 时间复杂度高,不适用于大输入。

方法二:优先队列(堆)(✅ 可行但非最优)

  • 使用最大堆存储 (值, 索引)
  • 每次取堆顶,若索引不在当前窗口内则弹出。
  • 时间复杂度:O(n log n),空间 O(n)。

方法三:单调双端队列(✅ 最优解)

核心思想 :维护一个单调递减的双端队列(deque),队首始终是当前窗口的最大值。

步骤详解:
  1. 初始化 :创建一个双端队列 deque 存储数组索引,结果列表 res
  2. 遍历数组 (i 从 0 到 n-1):
    • 移除队尾较小元素 :若 nums[i] >= nums[deque.back()],则不断弹出队尾,保持队列单调递减。
    • 加入当前索引 :将 i 加入队尾。
    • 移除队首过期索引 :若队首索引 <= i - k(即不在当前窗口),则弹出队首。
    • 记录结果 :当 i >= k - 1(即第一个完整窗口形成),将 nums[deque.front()] 加入结果。
  3. 返回结果数组。

为什么有效?

  • 队列中索引对应的值单调递减 → 队首是最大值。
  • 及时移除过期索引 → 保证队首在窗口内。
  • 每个元素最多入队出队一次 → 线性时间。

💻 代码实现

java:Java 复制代码
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || k <= 0) return new int[0];
        int n = nums.length;
        int[] res = new int[n - k + 1];
        Deque<Integer> deque = new ArrayDeque<>(); // 存储索引
        
        for (int i = 0; i < n; i++) {
            // 移除队尾:保持单调递减
            while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            
            // 移除队首过期索引
            if (deque.peekFirst() <= i - k) {
                deque.pollFirst();
            }
            
            // 记录结果(从第k个元素开始)
            if (i >= k - 1) {
                res[i - k + 1] = nums[deque.peekFirst()];
            }
        }
        return res;
    }
}
go:Go 复制代码
func maxSlidingWindow(nums []int, k int) []int {
    if len(nums) == 0 || k <= 0 {
        return []int{}
    }
    n := len(nums)
    res := make([]int, 0, n-k+1)
    deque := make([]int, 0) // 存储索引
    
    for i := 0; i < n; i++ {
        // 移除队尾:保持单调递减
        for len(deque) > 0 && nums[i] >= nums[deque[len(deque)-1]] {
            deque = deque[:len(deque)-1]
        }
        deque = append(deque, i)
        
        // 移除队首过期索引
        if deque[0] <= i-k {
            deque = deque[1:]
        }
        
        // 记录结果(从第k个元素开始)
        if i >= k-1 {
            res = append(res, nums[deque[0]])
        }
    }
    return res
}

🎯 示例演示

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

i nums[i] deque (索引) deque (值) 是否记录 res
0 1 [0] [1] []
1 3 [1] [3] []
2 -1 [1,2] [3,-1] [3]
3 -3 [1,2,3] [3,-1,-3] [3,3]
4 5 [4] [5] [3,3,5]
5 3 [4,5] [5,3] [3,3,5,5]
6 6 [6] [6] [3,3,5,5,6]
7 7 [7] [7] [3,3,5,5,6,7]

✅ 队列始终保持单调递减,队首为窗口最大值。


✅ 答案有效性证明

  1. 正确性

    • 队列单调递减 ⇒ 队首是当前窗口最大值。
    • 过期索引被及时移除 ⇒ 队首始终在窗口 [i-k+1, i] 内。
    • 每个窗口恰好记录一次 ⇒ 结果长度为 n - k + 1
  2. 边界处理

    • k = 1:每个元素单独成窗,结果即原数组。
    • k = n:整个数组一个窗口,结果为全局最大值。

📊 复杂度分析

方法 时间复杂度 空间复杂度 说明
暴力法 O(nk) O(1) 每个窗口遍历k次
优先队列 O(n log n) O(n) 堆操作log n
单调队列 O(n) O(k) 每个元素入队出队各一次

✅ 单调队列是最优解,线性时间,常数空间(忽略结果数组)。


📌 问题总结

  • 关键洞察 :滑动窗口最大值问题本质是动态维护区间最值 ,适合用单调队列
  • 技巧 :队列存索引而非值,便于判断是否过期。
  • 扩展:类似问题如"滑动窗口中位数"可用双堆,"最小值"只需改为单调递增队列。
  • 面试重点:单调队列是高频考点,需熟练掌握其维护逻辑与应用场景。
相关推荐
今心上2 小时前
spring中的@Autowired到底是什么
java·后端·spring
im_AMBER2 小时前
Leetcode 126 两数之和 II - 输入有序数组 | 盛最多水的容器
数据结构·学习·算法·leetcode
ShiJiuD6668889992 小时前
Java 异常 File
java·开发语言
lxl13072 小时前
C++算法(5)位运算
java·c++·算法
tankeven2 小时前
HJ96 表示数字
c++·算法
嵌入式×边缘AI:打怪升级日志2 小时前
C语言算术赋值运算复习笔记
c语言·stm32·单片机·算法·51单片机·proteus·代码
lxl13072 小时前
C++算法(4)前缀和
开发语言·c++·算法
不想看见4042 小时前
Minimum Path Sum 基本动态规划:二维--力扣101算法题解笔记
算法·leetcode·动态规划
啊阿狸不会拉杆2 小时前
《计算机视觉:模型、学习和推理》第 7 章-复杂数据密度建模
人工智能·python·学习·算法·计算机视觉·t分布·复杂数据密度建模