大约 2 分钟
方案一:暴力解法(修正你的代码)
var maxSlidingWindow = function(nums, k) {
if (k >= nums.length) return [Math.max(...nums)];
const result = [];
let left = 0, right = k - 1;
while (right < nums.length) {
// 使用 slice 获取窗口(不修改原数组),然后求最大值
const window = nums.slice(left, right + 1);
result.push(Math.max(...window));
left++;
right++;
}
return result;
};
方案二:单调队列(最优解,O(n))
=== 单调队列解法原理 ===
维护一个单调递减的双端队列(存索引)
队列特点:
1. 队列只维护索引,队首永远是当前窗口最大值的索引
2. 新元素入队前,先移除所有比它小的元素
3. 移除已出窗口的元素(队首索引 < i-k+1)
--- 处理 nums[0] = 1 ---
入队索引 0,队列: [0]
--- 处理 nums[1] = 3 ---
移除队尾 0(值 1 < 3)
入队索引 1,队列: [1]
--- 处理 nums[2] = -1 ---
入队索引 2,队列: [1, 2]
窗口: [1, 3, -1], 最大值: 3 (索引 1)
--- 处理 nums[3] = -3 ---
入队索引 3,队列: [1, 2, 3]
窗口: [3, -1, -3], 最大值: 3 (索引 1)
--- 处理 nums[4] = 5 ---
移除队首 1(已出窗口)
移除队尾 3(值 -3 < 5)
移除队尾 2(值 -1 < 5)
入队索引 4,队列: [4]
窗口: [-1, -3, 5], 最大值: 5 (索引 4)
--- 处理 nums[5] = 3 ---
入队索引 5,队列: [4, 5]
窗口: [-3, 5, 3], 最大值: 5 (索引 4)
--- 处理 nums[6] = 6 ---
移除队尾 5(值 3 < 6)
移除队尾 4(值 5 < 6)
入队索引 6,队列: [6]
窗口: [5, 3, 6], 最大值: 6 (索引 6)
--- 处理 nums[7] = 7 ---
移除队尾 6(值 6 < 7)
入队索引 7,队列: [7]
窗口: [3, 6, 7], 最大值: 7 (索引 7)
最终结果: [3, 3, 5, 5, 6, 7]
/**
* 单调队列解法(推荐)
* 时间复杂度: O(n)
*/
var maxSlidingWindow = function(nums, k) {
if (k >= nums.length) return [Math.max(...nums)];
const result = [];
const deque = []; // 双端队列,存储索引
for (let i = 0; i < nums.length; i++) {
// 1. 移除已出窗口的元素(队首索引 < i-k+1)
while (deque.length > 0 && deque[0] < i - k + 1) {
deque.shift();
}
// 2. 移除比当前元素小的元素(维护单调递减)
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
// 3. 当前索引入队
deque.push(i);
// 4. 窗口形成后,记录最大值(队首)
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
};
复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 暴力解法 | O(n×k) | O(1) |
| 单调队列 | O(n) | O(k) |
| 单调队列的核心思想:每个元素最多入队一次、出队一次,所以总时间复杂度是 O(n)! |