239. 滑动窗口最大值

124 阅读3分钟

题目

🔗题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)

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

思路

  1. 暴力循环。

    大力出奇迹

    一层是滑动窗口移动,一层是窗口内元素遍历查找

  2. 使用单调队列。

    单调队列如何维护队列里的元素:

    image.png

    创建一个单调队列,里面存储的元素的最大个数小于等于当前滑动窗口的大小且存入的数据是单调递减的。

    比如:

    当前窗口的元素 [1 3 -1] -3 5 3 6 7

    queue里存的数据 [3, -1] (队列左出右进)

    每次滑动窗口移动前都取队列中最大元素(出口处元素)放入结果数组

    注意:

    每次滑动窗口移动都要进行入队与出队操作

    入队时,要确保队列的单调递减性,保证元素从大到小排列。

    出队时,如果滑动窗口左侧元素在队列中(队列出口处元素等于滑动窗口左侧元素),那就让他出队。如果滑动窗口左侧元素不在队列中,说明在入队时,该元素已经出队,此时不操作队列。

    单调队列的工作过程:

    image.png

代码

/**
 * 单调队列
 *
 * 单调递减(从大到小)
 */
class MonotonicQueue {
  private queue: number[];

  constructor() {
    this.queue = [];
  }

  /**
   * 入队
   *
   * value 如果大于队尾元素,则将队尾元素删除,直至队尾元素大于 value,或者队列为空
   * @param value
   */
  public enqueue(value: number): void {
    // 队列可能为空
    let backValue: number | undefined = this.queue[this.queue.length - 1];

    // 如果队列末尾有值且小于将要入队的值
    // 那就删除队尾元素,直到队尾元素大于 value
    while (backValue !== undefined && backValue < value) {
      this.queue.pop();
      backValue = this.queue[this.queue.length - 1];
    }

    this.queue.push(value);
  }

  /**
   * 出队
   *
   * 只有当队头元素等于 value,才出队
   */
  public dequeue(value: number): void {
    let top: number | undefined = this.top();

    if (top !== undefined && top === value) {
      this.queue.shift();
    }
  }

  public top(): number | undefined {
    return this.queue[0];
  }
}

function maxSlidingWindow(nums: number[], k: number): number[] {
  const lens = nums.length;
  const resArr: number[] = [];
  // 维护一个在滑动窗口范围区间内的单调递减队列
  const monotonicQueue = new MonotonicQueue();

  let left = 0;
  let right = 0;

  // 先让最开始位置的滑动窗口里的元素入队
  while (right < k) {
    monotonicQueue.enqueue(nums[right]);
    right++;
  }
  // 这里使用 ! ,是因为已经有元素入队了 monotonicQueue.top() 不会为 undefined
  resArr.push(monotonicQueue.top()!);

  // 开始移动窗口
  while (right < lens) {
    // 让滑动窗口右边的元素入队
    monotonicQueue.enqueue(nums[right]);
    // 滑动窗口左侧元素出队
    // 存在就出队,不存在就不操作
    // 如果队列出口处的元素与滑动窗口左侧的元素相同,需要出队
    // 如果不相同,那就是在右侧元素在入队的时候,该元素已经被出队了(队列出口处与滑动窗口左侧的的元素相等的元素)
    // 这也是为什么出队函数 只有当队头元素等于 value,才出队
    monotonicQueue.dequeue(nums[left]);
    // 这里使用 ! ,是因为已经有元素入队了 monotonicQueue.top() 不会为 undefined
    resArr.push(monotonicQueue.top()!);

    // 窗口右移
    left++;
    right++;
  }

  return resArr;
}