(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 numsi 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) 每个元素入队出队各一次

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


📌 问题总结

  • 关键洞察 :滑动窗口最大值问题本质是动态维护区间最值 ,适合用单调队列。
  • 技巧 :队列存索引而非值,便于判断是否过期。
  • 扩展:类似问题如"滑动窗口中位数"可用双堆,"最小值"只需改为单调递增队列。
  • 面试重点:单调队列是高频考点,需熟练掌握其维护逻辑与应用场景。
相关推荐
智能直播1 小时前
RK3528 V4L2 框架 UVC 接口自动获取 H.264 / MJPEG / YUYV 格式:从内核采集到硬解分发
java·网络·实时音视频·视频编解码·h.264
171320330计算机毕设编程1 小时前
计算机毕业设计 基于YOLO改进算法的车辆特征识别系统设计与实现--lw
java·大数据·python·sql·算法
姜鱼问生1 小时前
Nginx gzip 压缩:一个配置让页面加载快 3 倍
后端·算法·架构
badhope1 小时前
Spring AI Alibaba停更:Java程序员的信任,还能被辜负几次?
java·spring
不会就选b1 小时前
算法日常・每日刷题--<动态规划>23
算法·动态规划
const_qiu1 小时前
第一章 1.1-1.2:基础模型调用与 TypeScript 入门 —— Java 开发者的视角
java·javascript·typescript
Wang's Blog1 小时前
Java 项目实战: 交友项目 - Dubbo 负载均衡策略与实战验证
java·dubbo·交友
SL_staff2 小时前
销售知识库7天落地实践:面向业务侧的零代码嵌入方案
java·开源·产品
GreatSQL社区2 小时前
主从复制SQL线程中断——字符集砸出的坑
java·数据库·算法