当前位置: 首页 > news >正文

LeetCode 239 滑动窗口最大值

滑动窗口最大值问题详解

问题背景

滑动窗口最大值问题是一个经典的算法问题,通常用于处理数组或列表数据。该问题要求我们找到一个数组中每个长度为 k 的连续子数组(滑动窗口)的最大值,并返回这些最大值组成的数组。这个问题在处理时间序列数据、实时数据流以及图像处理等领域中非常常见。

问题描述

给定一个整数数组 nums 和一个整数 k,表示滑动窗口的大小。滑动窗口从数组的最左侧开始,每次向右移动一位,直到到达数组的最右侧。对于每个位置的滑动窗口,我们需要找到其中的最大值,并将这些最大值按顺序存储在结果数组中。

暴力解法

最直观的思路是对于数组中的每个位置的窗口,依次遍历其中的元素,找到最大值。这种方法简单直接,但在窗口较大时效率较低。

实现思路

  1. 遍历数组,对于每个起始位置 i,计算窗口结束位置 i+k−1。

  2. 在子数组 nums[i...i+k−1] 中找到最大值。

  3. 将最大值存入结果数组。

实现代码

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if (nums == null || nums.length == 0 || k == 0) return new int[0];int n = nums.length;int[] result = new int[n - k + 1];for (int i = 0; i < n - k + 1; i++) {int max = nums[i];for (int j = i; j < i + k; j++) {if (nums[j] > max) {max = nums[j];}}result[i] = max;}return result;}
}
时间复杂度

该方法的时间复杂度为 O(n×k),其中 n 是数组长度。当 k 接近 n 时,时间复杂度接近 O(n2),效率较低。

优化方法:双端队列

为了提高效率,我们可以利用双端队列(Deque)来优化滑动窗口最大值的计算。双端队列可以帮助我们在每个窗口中快速找到最大值,从而将时间复杂度降至 O(n)。

关键思想

  1. 维护单调递减队列:队列中存储数组元素的索引,并且这些索引对应的值是单调递减的。

  2. 移除无效元素:当新元素进入窗口时,移除队列中所有比新元素小的元素,因为它们不可能成为后续窗口的最大值。

  3. 移除超出窗口范围的元素:当窗口滑动时,检查队列头部的元素是否仍在窗口范围内,若不在则移除。

详细步骤

  1. 初始化一个双端队列,用于存储可能成为最大值的元素的索引。

  2. 遍历数组中的每个元素:

    • 移除队列尾部所有比当前元素小的索引,因为它们不可能成为后续窗口的最大值。

    • 将当前元素的索引添加到队列尾部。

    • 移除队列头部超出当前窗口范围的索引。

    • 当窗口形成(即索引达到 k−1 时),将队列头部的元素值添加到结果数组中。

实现代码

import java.util.Deque;
import java.util.LinkedList;class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if (nums == null || nums.length == 0 || k == 0) return new int[0];int n = nums.length;Deque<Integer> deque = new LinkedList<>();int[] result = new int[n - k + 1];int resultIndex = 0;for (int i = 0; i < n; i++) {// Remove elements smaller than current from the end of dequewhile (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {deque.pollLast();}deque.offerLast(i);// Remove elements out of the window from the front of dequewhile (deque.peekFirst() <= i - k) {deque.pollFirst();}// Start adding to result when the window is formedif (i >= k - 1) {result[resultIndex++] = nums[deque.peekFirst()];}}return result;}
}

算法分析

时间复杂度

每个元素最多被添加和移除一次,因此时间复杂度为 O(n)。

空间复杂度

双端队列存储的元素数量最多为 k,因此空间复杂度为 O(k)。

示例

给定数组 nums = [1, 3, -1, -3, 5, 3, 6, 7]k = 3,滑动窗口的最大值数组为 [3, 3, 5, 5, 6, 7]

详细过程

  1. 初始状态:deque 为空,result 为空。

  2. 遍历数组:

    • i=0:添加索引 0 到 dequedeque = [0]

    • i=1:移除比 3 小的元素(索引 0),添加索引 1 到 dequedeque = [1]

    • i=2:添加索引 2 到 dequedeque = [1, 2]。窗口形成,result[0] = nums[1] = 3

    • i=3:移除比 -3 小的元素(无),添加索引 3 到 dequedeque = [1, 2, 3]。移除超出窗口范围的索引 1。deque = [2, 3]result[1] = nums[2] = -1?不对,需要重新检查。

    似乎在解释过程中出现了一个错误,让我重新详细地走一遍这个例子:

实际上,在例子 nums = [1, 3, -1, -3, 5, 3, 6, 7]k = 3 中,正确的结果应该是 [3, 3, 5, 5, 6, 7]。让我们详细模拟每一步:

  • i = 0

    • nums[i] = 1

    • deque 为空,直接添加索引 0。

    • deque = [0]

    • 窗口尚未形成(i < k-1),不添加到结果。

  • i = 1

    • nums[i] = 3

    • 移除 deque 尾部比 3 小的索引(0对应的值1 < 3,所以移除0)。

    • 添加索引1到 deque

    • deque = [1]

    • 窗口尚未形成(i < k-1),不添加到结果。

  • i = 2

    • nums[i] = -1

    • deque 尾部是索引1对应的值3,比-1大,所以直接添加索引2。

    • deque = [1, 2]

    • 窗口形成(i >= k-1),结果添加 nums[deque.front()] = nums[1] = 3

    • result = [3]

  • i = 3

    • nums[i] = -3

    • deque 尾部是索引2对应的值-1,比-3大,所以直接添加索引3。

    • deque = [1, 2, 3]

    • 检查窗口范围:deque.front() = 1,而 i−k+1=3−3+1=1,所以索引1在窗口范围内。

    • 结果添加 nums[1] = 3

    • result = [3, 3]

  • i = 4

    • nums[i] = 5

    • 移除 deque 尾部比5小的元素:索引3(-3)和索引2(-1)都比5小,所以移除。

    • 移除后 deque = [1]

    • 添加索引4到 deque

    • deque = [1, 4]

    • 检查窗口范围:deque.front() = 1,而 i−k+1=4−3+1=2,索引1 < 2,所以移除索引1。

    • deque = [4]

    • 结果添加 nums[4] = 5

    • result = [3, 3, 5]

  • i = 5

    • nums[i] = 3

    • deque 尾部是索引4对应的值5,比3大,所以直接添加索引5。

    • deque = [4, 5]

    • 检查窗口范围:i−k+1=5−3+1=3,索引4 >= 3,所以无需移除。

    • 结果添加 nums[4] = 5

    • result = [3, 3, 5, 5]

  • i = 6

    • nums[i] = 6

    • 移除 deque 尾部比6小的元素:索引5(3)和索引4(5)都比6小,所以移除。

    • deque 变为空。

    • 添加索引6到 deque

    • 检查窗口范围:i−k+1=6−3+1=4,索引6 >=4。

    • 结果添加 nums[6] = 6

    • result = [3, 3, 5, 5, 6]

  • i = 7

    • nums[i] = 7

    • 移除 deque 尾部比7小的元素:索引6(6)比7小,移除。

    • deque 变为空。

    • 添加索引7到 deque

    • 检查窗口范围:i−k+1=7−3+1=5,索引7 >=5。

    • 结果添加 nums[7] = 7

    • result = [3, 3, 5, 5, 6, 7]

这样,最终结果为 [3, 3, 5, 5, 6, 7]

应用场景

滑动窗口最大值问题在许多实际场景中都有应用,例如:

  1. 实时数据处理:在实时数据流中,需要快速找到最近一段时间内的最大值。例如,监控系统中每秒检测数据,需要实时显示过去5秒的最大值。

  2. 图像处理:在图像处理中,滑动窗口技术用于特征提取和滤波。例如,最大值滤波可以用于增强图像中的亮点。

  3. 时间序列分析:在金融数据、传感器数据等时间序列分析中,滑动窗口最大值可以帮助识别趋势和异常。例如,股票价格分析中,可以使用滑动窗口找到过去7天的最高价。

总结

滑动窗口最大值问题是一个经典的算法问题,通过使用双端队列可以高效地解决。这种方法不仅提高了算法的效率,还展示了数据结构在优化算法中的重要性。希望这篇文章能帮助你更好地理解和应用滑动窗口技术。

在实际应用中,选择合适的算法和数据结构至关重要。对于滑动窗口问题,双端队列提供了一种高效且优雅的解决方案。通过维护一个单调递减的队列,我们可以在 O(n) 的时间复杂度内完成计算,这对于处理大规模数据尤为重要。

相关文章:

  • 【机器学习】从炼丹到落地!模型部署与监控全流程实战指南 (MLOps 核心)
  • 【sylar-webserver】8 HOOK模块
  • Linux系统:进程终止的概念与相关接口函数(_exit,exit,atexit)
  • BT1120 BT656驱动相关代码示例
  • 计算机网络八股——HTTP协议与HTTPS协议
  • Linux疑难杂惑 | 云服务器重装系统后vscode无法远程连接的问题
  • 针对MCP认证考试中的常见技术难题进行实战分析与解决方案分享
  • Windows桌面图标变白的解决方案
  • springboot--web开发请求参数接收注解
  • GD32H7单片机使用segger_rtt,rtt-viewer看不到输出的问题,怎样解决?
  • 07-DevOps-安装部署Harbor镜像仓库
  • 小刚说C语言刷题——1035 判断成绩等级
  • 资源-又在网上淘到金了
  • csdn教程
  • 黑马点评秒杀优化
  • 手撕 简易HashMap
  • 2025.04.19-阿里淘天春招算法岗笔试-第二题
  • 【基础】回文数个数
  • Java—— 常见API介绍 第二期
  • 【数据结构_11】二叉树(3)
  • 徐丹任武汉大学药学院院长:研究领域在国际上处领跑地位
  • 印巴局势紧张或爆发军事冲突,印度空军能“一雪前耻”吗?
  • 中国空间站多项太空实验已取得成果,未来将陆续开展千余项研究
  • 市场监管总局出手整治涉企乱收费,聚焦政府部门及下属单位等领域
  • 徐徕任上海浦东新区副区长
  • 巴西外长维埃拉:国际形势日益复杂,金砖国家必须发挥核心作用