https://leetcode.cn/problems/sliding-window-maximum/description/?envType=study-plan-v2&envId=top-100-liked
给定一个整数数组 nums
和一个整数 k
,有一个大小为 k
的滑动窗口从数组的最左侧移动到最右侧。每次滑动窗口向右移动一位,返回每个滑动窗口中的最大值。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
示例 2:
输入:nums = [1], k = 1
输出:[1]
n ≤ 10^5
)。核心思想:
操作流程:
k
个元素,维护单调递减队列。优势:
k
个元素)。import java.util.*;
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];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 移除不在窗口范围内的索引
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 移除比当前元素小的索引
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// 加入当前索引
deque.offerLast(i);
// 记录窗口最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (!nums.length || k === 0) return [];
const n = nums.length;
const result = [];
const deque = [];
for (let i = 0; i < n; i++) {
// 移除不在窗口范围内的索引
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
// 移除比当前元素小的索引
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 加入当前索引
deque.push(i);
// 记录窗口最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
暴力法 | O(nk) | O(1) |
单调队列法 | O(n) | O(k) |
Deque
接口的实现类 ArrayDeque
。shift()
和 pop()
的性能)。掌握这道题,你就能高效解决滑动窗口最大值问题!
滑动窗口最大值问题是算法中的一个常见问题,它要求我们在一个固定大小的窗口中找到最大值。在每次窗口滑动时,我们需要更新最大值。本文将分别使用Java和JavaScript来解答这个问题。
给定一个整数数组 nums
和一个整数 k
,表示窗口的大小。我们需要计算每个窗口中的最大值,并返回一个包含所有窗口最大值的数组。
我们可以使用一个双端队列(Deque)来维护窗口中的最大值。具体步骤如下:
deque
,它将存储窗口中的元素索引。nums
:
import java.util.ArrayDeque;
import java.util.Deque;
public class MaxSlidingWindow {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0) {
return new int[0];
}
Deque<Integer> deque = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 移除不在窗口内的元素
if (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 移除小于当前元素的值
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// 添加当前元素的索引
deque.offerLast(i);
// 如果窗口已满,则记录最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
JavaScript的实现与Java类似,只是语法上有所不同。
function maxSlidingWindow(nums, k) {
if (!nums || nums.length === 0 || k <= 0) {
return [];
}
let deque = [];
let result = [];
for (let i = 0; i < nums.length; i++) {
// 移除不在窗口内的元素
if (deque.length && deque[0] < i - k + 1) {
deque.shift();
}
// 移除小于当前元素的值
while (deque.length && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 添加当前元素的索引
deque.push(i);
// 如果窗口已满,则记录最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
}
通过本文,我们学习了如何使用Java和JavaScript解决滑动窗口最大值问题。使用双端队列来维护窗口中的最大值是一种有效的方法,它允许我们在O(n)的时间复杂度内完成整个数组的遍历。希望这篇文章能帮助你更好地理解并掌握这一知识点。
好的,我很乐意为你提供详细的解答和博客内容。以下是一篇新手入门博客,包含Java和JavaScript的解答以及相关知识点的详细介绍。
博客标题: 从零开始学习力扣题 239. 滑动窗口最大值
目录:
问题描述
Java解答
2.1 使用双端队列
JavaScript解答
3.1 使用双端队列
知识点总结
4.1 滑动窗口
4.2 双端队列
4.3 时间复杂度和空间复杂度
问题描述
力扣题 239. 滑动窗口最大值要求实现一个函数 maxSlidingWindow(nums, k)
,其中 nums
是一个整数数组, k
是滑动窗口的大小。该函数需要返回滑动窗口中的最大值。
Java解答
2.1 使用双端队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return new int[0];
}
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < n; i++) {
// 移除队列中小于当前元素的值
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// 添加当前元素索引
deque.offerLast(i);
// 移除队列中超出滑动窗口的元素
if (deque.peekFirst() == i - k) {
deque.pollFirst();
}
// 当滑动窗口大小达到 k 时,将最大值添加到结果数组
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
3.1 使用双端队列
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (!nums || nums.length === 0) {
return [];
}
const n = nums.length;
const result = [];
const deque = [];
for (let i = 0; i < n; i++) {
// 移除队列中小于当前元素的值
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 添加当前元素索引
deque.push(i);
// 移除队列中超出滑动窗口的元素
if (deque[0] === i - k) {
deque.shift();
}
// 当滑动窗口大小达到 k 时,将最大值添加到结果数组
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
4.1 滑动窗口
滑动窗口是一种常见的数据结构,它可以用来解决一些需要维护一个固定大小窗口的问题,比如求最大值、最小值等。
4.2 双端队列
双端队列是一种特殊的队列,它允许在队列的两端进行插入和删除操作。在本题中,我们使用双端队列来维护滑动窗口中的最大值。
4.3 时间复杂度和空间复杂度
maxSlidingWindow()
方法的时间复杂度为 O(n),空间复杂度为 O(k),其中 n 是数组的长度,k 是滑动窗口的大小。在处理连续区间最大值的问题时,“滑动窗口最大值”是经典也是非常实用的一题。它不仅考察你的数据结构基础,还锻炼你的算法优化能力。本文将逐步讲解此题的思路,并用Java和JavaScript两种语言完整实现,帮助你理解和掌握解决方案。
题目描述:
给定一个数组nums
和一个滑动窗口大小k
,窗口在数组上从左到右滑动,每次移动一格。每次滑动后,求窗口内的最大值。
示例:
输入: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
提示:
利用一个双端队列(Deque)存储当前窗口可能的最大值的索引:
i - k + 1
),就弹出队首优点:
import java.util.Deque;
import java.util.LinkedList;
public class SlidingWindowMaximum {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k == 0) return new int[0];
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < n; i++) {
// 移除队尾所有比当前元素小的索引
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 移除超出窗口范围的索引
if (deque.peekFirst() == i - k) {
deque.pollFirst();
}
// 收集结果
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
public static void main(String[] args) {
SlidingWindowMaximum solution = new SlidingWindowMaximum();
int[] nums = {1,3,-1,-3,5,3,6,7};
int[] res = solution.maxSlidingWindow(nums, 3);
for (int v : res) {
System.out.print(v + " ");
}
// 输出:3 3 5 5 6 7
}
}
var maxSlidingWindow = function(nums, k) {
const result = [];
const deque = []; // 存索引
for (let i = 0; i < nums.length; i++) {
// 移除队尾所有比当前元素小的索引
while (deque.length && nums[deque[deque.length -1]] < nums[i]) {
deque.pop();
}
deque.push(i);
// 移除超出窗口范围的索引
if (deque[0] === i - k) {
deque.shift();
}
// 收集结果
if (i >= k -1) {
result.push(nums[deque[0]]);
}
}
return result;
};
// 示例测试
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3)); // [3,3,5,5,6,7]
利用双端队列(队列的单调性特点)是解决“滑动窗口最大值”问题的标准方案。这种技巧不仅在此题中有用,还可以应用到诸如滑动窗口平均值、区间判定等问题中。理解和掌握这种数据结构的用法,将极大增强你的算法能力。
祝你练习顺利,算法之路越走越宽!
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
暴力解法的思路是,对于每一个滑动窗口,遍历窗口内的元素,找到最大值。这个方法的时间复杂度为 (O(n \cdot k)),在最坏情况下可能会超时。
为了提高效率,我们可以使用双端队列(Deque)来维护当前滑动窗口的最大值。具体步骤如下:
k
时,队列的头部就是当前窗口的最大值。这种方法的时间复杂度为 (O(n)),因为每个元素最多被添加和删除一次。
以下是 Java 的实现代码:
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// Remove elements not in the sliding window
if (!deque.isEmpty() && deque.peek() < i - k + 1) {
deque.poll();
}
// Remove elements smaller than the current element
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// Add current element's index
deque.offer(i);
// Add the maximum for the current window
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peek()];
}
}
return result;
}
}
以下是 JavaScript 的实现代码:
var maxSlidingWindow = function(nums, k) {
if (nums.length === 0) return [];
const result = [];
const deque = []; // 存储索引
for (let i = 0; i < nums.length; i++) {
// 移除不在滑动窗口的元素
if (deque.length && deque[0] < i - k + 1) {
deque.shift();
}
// 移除所有小于当前元素的索引
while (deque.length && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 添加当前元素的索引
deque.push(i);
// 添加当前窗口的最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
在这篇博客中,我们讨论了力扣题目“239. 滑动窗口最大值”的解法。我们首先介绍了题目的基本要求,然后分析了使用双端队列的方法来高效地找到滑动窗口中的最大值。最后,我们提供了 Java 和 JavaScript 的实现代码。
通过这种方法,我们能够在 (O(n)) 的时间复杂度内处理数据流,适合处理较大的输入数据。希望这篇博客能帮助你更好地理解这个问题及其解法!如果你有任何问题或想法,欢迎在评论区留言讨论。
好的,为你准备一篇关于 LeetCode 239. 滑动窗口最大值的详细入门博客,包含 Java 和 JavaScript 的解法,并深入讲解算法思路和复杂度分析。
博客标题: LeetCode 239. 滑动窗口最大值:新手入门指南 (Java & JavaScript)
目录:
博客正文:
1. 引言:滑动窗口中的最大值
“滑动窗口最大值”是 LeetCode 上一道经典的困难难度题目。它考察了你对队列这种数据结构的理解和应用,以及你解决问题的能力。掌握这道题的解法,对于你提升算法思维非常有帮助。
2. 题目描述
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例:
输入: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
输入:nums = [1], k = 1
输出:[1]
3. 解题思路:单调队列
3.1 为什么选择单调队列?
3.2 算法步骤
deque
),用于存储数组的索引。nums
。nums[i]
:
nums[i]
的元素都移除。 这是为了保证队列是单调递减的。i
添加到队列的末尾。deque.peekFirst() < i - k + 1
),将队列的头部元素移除。i >= k - 1
,将队列的头部元素(即当前滑动窗口的最大值的索引)对应的 nums[deque.peekFirst()]
添加到结果数组中。4. Java 代码实现
4.1 代码
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n * k == 0) return new int[0];
if (k == 1) return nums;
Deque<Integer> deque = new LinkedList<>();
int[] result = new int[n - k + 1];
for (int i = 0; i < n; i++) {
// 移除队列中小于当前元素的元素
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.removeLast();
}
// 添加当前元素的索引到队列
deque.addLast(i);
// 移除队列中不在滑动窗口中的元素
if (deque.peekFirst() < i - k + 1) {
deque.removeFirst();
}
// 如果滑动窗口已经形成,将最大值添加到结果数组
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
4.2 代码解释
Deque deque = new LinkedList<>();
: 创建一个双端队列。while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { deque.removeLast(); }
: 移除队列中小于当前元素的元素。deque.addLast(i);
: 添加当前元素的索引到队列。if (deque.peekFirst() < i - k + 1) { deque.removeFirst(); }
: 移除队列中不在滑动窗口中的元素。if (i >= k - 1) { result[i - k + 1] = nums[deque.peekFirst()]; }
: 如果滑动窗口已经形成,将最大值添加到结果数组。5. JavaScript 代码实现
5.1 代码
function maxSlidingWindow(nums, k) {
const n = nums.length;
if (n * k === 0) return [];
if (k === 1) return nums;
const deque = [];
const result = new Array(n - k + 1);
for (let i = 0; i < n; i++) {
// 移除队列中小于当前元素的元素
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 添加当前元素的索引到队列
deque.push(i);
// 移除队列中不在滑动窗口中的元素
if (deque[0] < i - k + 1) {
deque.shift();
}
// 如果滑动窗口已经形成,将最大值添加到结果数组
if (i >= k - 1) {
result[i - k + 1] = nums[deque[0]];
}
}
return result;
}
5.2 代码解释
const deque = [];
: 创建一个双端队列。while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) { deque.pop(); }
: 移除队列中小于当前元素的元素。deque.push(i);
: 添加当前元素的索引到队列。if (deque[0] < i - k + 1) { deque.shift(); }
: 移除队列中不在滑动窗口中的元素。if (i >= k - 1) { result[i - k + 1] = nums[deque[0]]; }
: 如果滑动窗口已经形成,将最大值添加到结果数组。6. 复杂度分析
6.1 时间复杂度
while
循环,但是每个元素最多只会被添加到队列一次,最多只会被移除队列一次。6.2 空间复杂度
7. 总结:掌握单调队列,高效解决滑动窗口问题!
“滑动窗口最大值”是一道经典的算法题,通过学习这道题,你可以掌握单调队列这种重要的数据结构,并学会如何使用它来解决实际问题。 希望这篇博客能帮助你入门算法和数据结构,并在你的编程之路上助你一臂之力! 继续学习和实践,你一定会成为一名优秀的程序员!
在算法学习的道路上,滑动窗口问题是一类经典且常见的问题。力扣第 239 题“滑动窗口最大值”就是这类问题中的一个典型代表。本题要求我们在一个整数数组中,找出每个固定大小的滑动窗口内的最大值。接下来,我们将详细分析这道题,并分别给出 Java 和 JavaScript 的实现方案。
给定一个整数数组 nums
和一个大小为 k
的滑动窗口,该窗口从数组的最左侧移动到最右侧,每次只向右移动一位。我们需要返回每个滑动窗口内的最大值。
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 |nums = [1]
,k = 1
[1]
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
为了高效地解决这个问题,我们可以使用单调队列。单调队列是一种特殊的数据结构,它可以在 O ( 1 ) O(1) O(1) 的时间复杂度内获取队列中的最大值。具体步骤如下:
deque
,用于存储数组元素的下标。nums
:
k - 1
个元素及以后时,队列头部元素对应的数组值就是当前滑动窗口的最大值,将其加入结果数组。import java.util.ArrayDeque;
import java.util.Deque;
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];
// 双端队列,存储数组元素的下标
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 移除队列尾部小于当前元素的元素
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
// 将当前元素的下标加入队列尾部
deque.offerLast(i);
// 移除队列头部超出滑动窗口范围的元素
if (deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 当遍历到第 k - 1 个元素及以后时,记录当前滑动窗口的最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = solution.maxSlidingWindow(nums, k);
for (int num : result) {
System.out.print(num + " ");
}
}
}
deque
是一个双端队列,用于存储数组元素的下标。while
循环移除队列尾部小于当前元素的元素,保证队列的单调性。pollFirst
方法移除队列头部元素。k - 1
个元素及以后时,将队列头部元素对应的数组值加入结果数组。var maxSlidingWindow = function(nums, k) {
if (nums.length === 0 || k <= 0) {
return [];
}
const n = nums.length;
const result = [];
// 双端队列,存储数组元素的下标
const deque = [];
for (let i = 0; i < n; i++) {
// 移除队列尾部小于当前元素的元素
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 将当前元素的下标加入队列尾部
deque.push(i);
// 移除队列头部超出滑动窗口范围的元素
if (deque[0] <= i - k) {
deque.shift();
}
// 当遍历到第 k - 1 个元素及以后时,记录当前滑动窗口的最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
// 测试代码
const nums = [1, 3, -1, -3, 5, 3, 6, 7];
const k = 3;
const result = maxSlidingWindow(nums, k);
console.log(result);
deque
是一个数组,用于模拟双端队列,存储数组元素的下标。while
循环移除队列尾部小于当前元素的元素,保证队列的单调性。shift
方法移除队列头部元素。k - 1
个元素及以后时,将队列头部元素对应的数组值加入结果数组。本题通过使用单调队列的方法,高效地解决了滑动窗口最大值的问题。在 Java 中,我们可以使用 ArrayDeque
来实现双端队列;在 JavaScript 中,我们可以使用数组来模拟双端队列。两种实现的时间复杂度和空间复杂度相同,都能满足题目的要求。希望这篇博客能帮助新手朋友们更好地理解和解决这道题目。
给定一个整数数组 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]
i - k
)。i >= k - 1
),记录队列头部的最大值。时间复杂度: O(n)
空间复杂度: O(k)(队列最多存储 k 个元素)
import java.util.ArrayDeque;
import java.util.Deque;
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];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 移除不在窗口内的索引
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 移除队列尾部比当前元素小的索引
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i); // 加入当前索引
// 窗口形成后,记录最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (!nums.length || k <= 0) return [];
const n = nums.length;
const result = [];
const deque = []; // 存储索引
for (let i = 0; i < n; i++) {
// 移除不在窗口内的索引
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
// 移除队列尾部比当前元素小的索引
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
deque.push(i); // 加入当前索引
// 窗口形成后,记录最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
双端队列 | O(n) | O(k) |
暴力法 | O(nk) | O(1) |
相关推荐:LeetCode 滑动窗口专题
如果有帮助,请点赞收藏支持!
题目:给定一个整数数组 nums
和一个窗口大小 k
,窗口从数组最左端滑动到最右端,每次向右移动一位。返回每个窗口中的最大值。
示例:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
[1,3,-1]
的最大值是 3
。[3,-1,-3]
的最大值是 3
。[3,6,7]
的最大值是 7
。思路:
k
个元素,找到最大值。i
,窗口范围为 [i, i+k-1]
。问题:
n=1e5
时会超时。使用单调队列维护窗口内可能成为最大值的元素:
步骤:
<= i-k
)。i >=k-1
),队头即为最大值。import java.util.ArrayDeque;
import java.util.Deque;
import java.util.ArrayList;
public class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
Deque<Integer> deque = new ArrayDeque<>();
List<Integer> result = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
// 移除过期的头部元素
while (!deque.isEmpty() && deque.peek() <= i - k) {
deque.poll();
}
// 维护单调性:弹出队尾所有小于当前元素的索引
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offer(i);
// 窗口形成后记录最大值
if (i >= k - 1) {
result.add(nums[deque.peek()]);
}
}
// 转换为数组
int[] res = new int[result.size()];
for (int j = 0; j < res.length; j++) {
res[j] = result.get(j);
}
return res;
}
}
关键点:
ArrayDeque
实现高效插入和删除。/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
const deque = []; // 存储索引
const result = [];
for (let i = 0; i < nums.length; i++) {
// 维护单调性:弹出队尾所有小于当前元素的索引
while (deque.length > 0 && nums[i] >= nums[deque[deque.length - 1]]) {
deque.pop();
}
deque.push(i);
// 处理过期的头部元素(在记录结果时检查)
if (i >= k - 1) {
// 移除过期的头部元素
while (deque[0] <= i - k) {
deque.shift();
}
result.push(nums[deque[0]]);
}
}
return result;
};
关键点:
push
和 pop
操作维护队尾,用 shift
处理队头。k
个元素(窗口内元素)。Q1:为什么队列要保存索引而不是数值?
Q2:为什么队列需要保持递减?
Q3:JavaScript 的 shift()
操作会不会影响性能?
shift
一次,总时间复杂度仍为 O(n)。通过本篇博客,你掌握了如何用单调队列高效解决滑动窗口最大值问题。无论是 Java 还是 JavaScript,核心逻辑一致,仅需调整语法细节即可。
给定一个整数数组 nums
和滑动窗口的大小 k
,窗口从数组最左侧移动到最右侧,每次移动一位。要求返回每个窗口中的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
关键要求:
方法 | 核心思路 | 时间复杂度 | 空间复杂度 |
---|---|---|---|
暴力法 | 每个窗口重新扫描找最大值 | O(nk) | O(1) |
单调队列 | 动态维护递减队列获取最大值 | O(n) | O(k) |
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 移除窗口外的元素
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 维护递减队列
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
// 记录窗口最大值
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
deque.peekFirst() < i - k + 1 // 检查队列头是否在窗口左侧之外
nums[i] >= nums[deque.peekLast()] // 新元素比队尾大就弹出队尾
i >= k - 1 // 当窗口完全进入数组后开始记录
public static void main(String[] args) {
Solution sol = new Solution();
int[] test1 = {1,3,-1,-3,5,3,6,7};
System.out.println(Arrays.toString(sol.maxSlidingWindow(test1, 3)));
// 输出 [3,3,5,5,6,7]
}
const maxSlidingWindow = (nums, k) => {
if (nums.length === 0 || k === 0) return [];
const deque = [];
const result = [];
for (let i = 0; i < nums.length; i++) {
// 移除窗口外的元素
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
// 维护递减队列
while (deque.length > 0 && nums[i] >= nums[deque[deque.length - 1]]) {
deque.pop();
}
deque.push(i);
// 记录窗口最大值
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
deque.shift() // 移除队头
deque.pop() // 移除队尾
deque.push() // 加入队尾
deque存储的是元素索引而非值,方便进行窗口位置检查
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3));
// 输出 [3,3,5,5,6,7]
shift()
,整体操作次数仍为O(n)特性 | Java | JavaScript |
---|---|---|
队列实现 | 标准库Deque |
数组模拟 |
元素移除效率 | O(1)(链表实现) | O(1)平均(引擎优化) |
空数组处理 | 显式检查返回空数组 | 同左 |
索引存储 | 必须存储 | 必须存储 |
// 错误示例:无法进行窗口位置检查
deque.add(nums[i]);
解决方法:必须存储元素索引
// 错误:当k=0时会导致死循环
if (k === 0) return [];
// 错误:结果数组索引计算错误
result[i] = ... // 正确应为 result[i - k + 1]
学习建议:
尝试使用其他数据结构(如优先队列)解决此题,比较不同方法的性能差异。
掌握单调队列的核心在于理解其动态维护最值的能力。这种数据结构在滑动窗口类问题中展现出惊人的效率,是算法工程师必须掌握的利器。下次遇到需要动态获取窗口极值的问题时,记得祭出这个法宝!
目录
给定一个整数数组 nums
和一个整数 k
,滑动窗口从数组最左侧移动到最右侧,每次窗口向右滑动一位。返回每次滑动窗口中的最大值。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
单调队列(双端队列):
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int idx = 0;
for (int i = 0; i < nums.length; i++) {
// 移除窗口外的队首元素
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// 维护单调递减性
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// 窗口形成后记录最大值
if (i >= k - 1) {
res[idx++] = nums[deque.peekFirst()];
}
}
return res;
}
}
function maxSlidingWindow(nums, k) {
const deque = [];
const res = [];
for (let i = 0; i < nums.length; i++) {
// 移除窗口外的队首元素
while (deque.length > 0 && deque < i - k + 1) {
deque.shift();
}
// 维护单调递减性
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
deque.push(i);
// 窗口形成后记录最大值
if (i >= k - 1) {
res.push(nums[deque]);
}
}
return res;
}
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
遍历数组 | O(n) | O(k) |
Java | 双端队列操作 O(1) | 结果数组 O(n) |
JavaScript | 数组模拟队列 O(n) | 结果数组 O(n) |
Java 测试:
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1,3,-1,-3,5,3,6,7};
int[] result = solution.maxSlidingWindow(nums, 3);
System.out.println(Arrays.toString(result)); // [3,3,5,5,6,7]
}
JavaScript 测试:
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3)); // [3,3,5,5,6,7]
Deque
双端队列,JavaScript 需用数组手动维护(可优化为指针避免 shift
的低效操作)。参考资料
单调队列原理与实现逻辑
双端队列操作与复杂度分析