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

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


📌 问题总结

  • 关键洞察 :滑动窗口最大值问题本质是动态维护区间最值 ,适合用单调队列
  • 技巧 :队列存索引而非值,便于判断是否过期。
  • 扩展:类似问题如"滑动窗口中位数"可用双堆,"最小值"只需改为单调递增队列。
  • 面试重点:单调队列是高频考点,需熟练掌握其维护逻辑与应用场景。
相关推荐
摇滚侠7 分钟前
讲一讲 SpringMVC,线程变量 ThreadLocal 的使用
java·spring boot·intellij-idea
qq_4017004110 分钟前
顺序、二分、插值、斐波那契查找算法
数据结构·算法·排序算法
x_xbx11 分钟前
LeetCode:26. 删除有序数组中的重复项
数据结构·算法·leetcode
WitsMakeMen12 分钟前
RoPE 算法原理?算法为什么只和相对位置有关
人工智能·算法·llm
sssdxiaokeyy13 分钟前
如何通过GEO优化让厂家销量飙升?
go
kuntli29 分钟前
BIO NIO AIO核心区别解析
java
0 0 029 分钟前
CCF-CSP 38-4 月票发行【C++】考点:动态规划DP+矩阵快速幂
c++·算法·动态规划·矩阵快速幂
北漂Zachary32 分钟前
Mysql中使用sql语句生成雪花算法Id
sql·mysql·算法
Javatutouhouduan36 分钟前
京东内部强推HotSpot VM源码剖析笔记(2026新版)
java·jvm·java虚拟机·校招·java面试·java程序员·互联网大厂
imuliuliang1 小时前
怎么下载安装yarn
java