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

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


📌 问题总结

  • 关键洞察 :滑动窗口最大值问题本质是动态维护区间最值 ,适合用单调队列
  • 技巧 :队列存索引而非值,便于判断是否过期。
  • 扩展:类似问题如"滑动窗口中位数"可用双堆,"最小值"只需改为单调递增队列。
  • 面试重点:单调队列是高频考点,需熟练掌握其维护逻辑与应用场景。
相关推荐
云烟成雨TD16 分钟前
Spring AI 1.x 系列【51】可观测性技术选型
java·人工智能·spring
星越华夏17 分钟前
ESP32-CAM图像传输项目说明文档
java·后端·struts·esp32
如竟没有火炬36 分钟前
最大矩阵——单调栈
数据结构·python·线性代数·算法·leetcode·矩阵
Jinkxs1 小时前
Java 跨域14-Java 与区块链(Hyperledger)集成
java·开发语言·区块链
8Qi81 小时前
LeetCode 1143 & 718:最长公共子序列 / 最长重复子数组
算法·leetcode·职场和发展·动态规划
绿算技术2 小时前
万卡推理集群存储选型分析:从核心架构到应用视角
大数据·科技·算法·架构
晨曦中的暮雨2 小时前
Golang速通(Javaer版)
java·开发语言·后端·golang
七老板的blog2 小时前
当 Spring StateMachine 遇见大模型:构建工业级 AI 写作流水线
java·人工智能·spring
想吃火锅10052 小时前
【leetcode】1.两数之和js版
javascript·算法·leetcode