在算法问题中,滑动窗口最大值是一类经典问题,要求从数组中依次提取每个固定大小的窗口,并快速找出其中的最大值。这类问题在实时数据处理、时间序列分析和性能监控等场景中有着广泛应用。本文将介绍如何利用单调队列这一高效数据结构,以O(n)的时间复杂度优雅地解决该问题,避免暴力解法带来的性能瓶颈。通过维护一个严格递减的索引队列,我们能够即时获取每个窗口的极值,同时保持最优的空间效率。
题目回顾
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。 你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。
思路解析
我们需要在一个大小为k的数组中找到一个最大值,可以构造一个求最大值的函数(不推荐,耗时)还可以直接构造一个递减队列(推荐)来解决。最大值永远是队列的对底元素。解决了最大值的问题后,我们需要考虑,当遍历数组时,遍历的当前值 > 对顶元素时 我们可以把对顶元素出队列,但不是单单只出一个对顶元素,直到有 对顶元素 > 当前元素 时或者队列里面没有元素时 再将遍历的当前值入队列 (解释:因为入队列是从给定的数组的下标一个一个入的,如果后面有值大于前面的话,那么最大值一定不可能在前面,那么我们就可以选择直接把前面的值直接去除掉) 遍历的当前值 < 对顶元素时 那么我们将它入队列。(解释:因为后面的值小的话,又因为是在一个区间,当前面的最大值脱离这个区间去除掉后,后面较小的值可能成为最大值。)入队列我们同时还需要考虑队列的长度是否已经超出,假如超出的话现将队底元素去除,再入队列。
代码实现
var maxSlidingWindow = function (nums, k) {
const queue = [] //创建一个单调递减的队列
const res = [] //创建一个数组用来记录区间内的最大值
for (let i = 0; i < nums.length; i++) {
// 如果当前元素 > 对顶元素
while (queue.length && nums[i] > nums[queue[queue.length - 1]]) {
queue.pop()
}
// 当当前队列中没有元素时,直接添加进去一个元素(添加的是下标)
// 当当前元素小于对顶元素时,添加进队列
queue.push(i)
if ([queue[queue.length - 1] - k + 1] > queue[0]) {
queue.shift()
}
if(queue[queue.length - 1] >= k - 1){
res.push(nums[queue[0]])
}
}
if (queue[queue.length - 1] === nums.length - 1) {
return res
}
};
实例演示
实例
输入:nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
输出[ 3, 3, 5, 5, 6, 7 ]
总结
复杂度分析
- 时间复杂度:O(n),每个元素最多入队和出队一次
- 空间复杂度:O(k),队列最多存储k个元素
常见错误点
- 边界条件处理:
- 当k=0或nums为空时的处理
- 当k=1时的特殊情况
- 索引计算错误:
- 窗口范围的判断容易出错(应该是i - queue[0] >= k)
- 结果记录的时机(i >= k-1)
- 性能优化:
- 避免不必要的队列操作
- 使用索引而不是值进行比较
这个解法使用了单调队列的思想,是解决滑动窗口最大值问题的标准方法,既高效又简洁。