[239. 滑动窗口最大值]

109 阅读4分钟

239. 滑动窗口最大值

困难

给你一个整数数组 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]

 

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

双指针+遍历 超时算法但思路清晰 O(kn)

function maxSlidingWindow(nums: number[], k: number): number[] {
    const len = nums.length
    const res: number[] = []
    let left = 0
    let right = k-1
    while(right < len){
    // 计算当前窗口内的最大值
        const max = calMax(nums,left,right) as number
        res.push(max);
        left ++
        right++
    }
    return res
};
// 这个函数用来计算最大值
function calMax(arr: number[],left: number,right: number): number {
    let maxNum = arr[left]
    for(let i=left;i<=right;i++){
        if(arr[i]> maxNum){
            maxNum = arr[i]
        }
    }
    return maxNum
}

思路分析:双端队列法 O(n)

使用双端队列法,核心的思路是维护一个有效的递减队列

每尝试推入一个元素前,都把这个元素与队列尾部的元素作对比。根据对比结果的不同,采取不同的措施:

  • 如果试图推入的元素(当前元素)大于队尾元素,则意味着队列的递减趋势被打破了。此时我们需要将队列尾部的元素依次出队(注意由于是双端队列,所以队尾出队是没有问题的)、直到队尾元素大于等于当前元素为止,此时再将当前元素入队。
  • 如果试图推入的元素小于队列尾部的元素,那么就不需要额外的操作,直接把当前元素入队即可。

接下来,每往前遍历一个元素,都需要重复以上的几个步骤。这里我总结一下每一步都做了什么:

  1. 检查队尾元素,看是不是都满足大于等于当前元素的条件。如果是的话,直接将当前元素入队。否则,将队尾元素逐个出队、直到队尾元素大于等于当前元素为止。
  2. 将当前元素入队
  3. 检查队头元素,看队头元素是否已经被排除在滑动窗口的范围之外了。如果是,则将队头元素出队。
  4. 判断滑动窗口的状态:看当前遍历过的元素个数是否小于 k。如果元素个数小于k,这意味着第一个滑动窗口内的元素都还没遍历完、第一个最大值还没出现,此时我们还不能动结果数组,只能继续更新队列;如果元素个数大于等于k,这意味着滑动窗口的最大值已经出现了,此时每遍历到一个新元素(也就是滑动窗口每往前走一步)都要及时地往结果数组里添加当前滑动窗口对应的最大值(最大值就是此时此刻双端队列的队头元素)。

这四个步骤分别有以下的目的:

  1. 维持队列的递减性:确保队头元素是当前滑动窗口的最大值。这样我们每次取最大值时,直接取队头元素即可。
  2. 这一步没啥好说的,就是在维持队列递减性的基础上、更新队列的内容。
  3. 维持队列的有效性:确保队列里所有的元素都在滑动窗口圈定的范围以内。
  4. 排除掉滑动窗口还没有初始化完成、第一个最大值还没有出现的特殊情况。

结合以上的分析,我们来写代码:

function maxSlidingWindow(nums: number[], k: number): number[] {
  const len: number = nums.length;
  const res: number[] = [];
  const deque: number[] = [];
  for (let i = 0; i < len; i++) {
    //当队尾小于当前元素,将队尾元素(索引)不断出队,直至队尾元素大于等于当前元素
    while (deque.length && nums[deque[deque.length - 1]] <= nums[i]) {
      deque.pop();
    }
    // 入队当前元素索引(注意是索引)
    deque.push(i);
    // 当队头元素的索引已经被排除在滑动窗口之外时 将队头元素索引出队

    while (deque.length && deque[0] <= i - k) {
      deque.shift();
    }
    // 判断滑动窗口的状态,只有在被遍历的元素个数大于 k 的时候,才更新结果数组
    if (i >= k - 1) {
      res.push(nums[deque[0]]);
    }
    //每轮结果
    // [ 0 ]
    // [ 1 ]
    // [ 1, 2 ]
    // [ 1, 2, 3 ]
    // [ 4 ]
    // [ 4, 5 ]
    // [ 6 ]
    // [ 7 ]
  }
  return res;
}