一分钟解决 | 高频面试算法题——滑动窗口最大值(单调队列)

一、前置知识(可跳过)

什么是双端队列(Deque)?

双端队列(Double-Ended Queue,简称 Deque)是一种允许在两端进行插入和删除操作的线性数据结构。它结合了队列和栈的特性。

Deque 的特点:

  • 双向操作: 可以在队列的头部和尾部进行添加和删除操作。
  • 灵活性: 可以用作队列(FIFO)或栈(LIFO)。

Deque 的基本操作:

  • push_back(element)push(element): 在队列尾部添加一个元素。
  • push_front(element)unshift(element): 在队列头部添加一个元素。
  • pop_back()pop(): 从队列尾部删除一个元素。
  • pop_front()shift(): 从队列头部删除一个元素。
  • peek_front()front(): 查看队列头部的元素(不删除)。
  • peek_back()back(): 查看队列尾部的元素(不删除)。
  • isEmpty(): 检查队列是否为空。
  • size(): 返回队列中元素的数量。

二、题目描述------滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k **的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

ini 复制代码
输入: 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:

ini 复制代码
输入: nums = [1], k = 1
输出: [1]

提示:

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

三、题解

超时题解(本人)

js 复制代码
var maxSlidingWindow = function(nums, k) {
    let left=0;
    let right=k-1;
    let ans=nums[0];
    let anss=[];
    if(k===0||nums.length===0) return [];
    if(nums.length===1) return [nums[0]];
    if (k===1){
        for(let i=0;i<nums.length;i++){
            anss.push(nums[i]);
        }
        return anss;
    }
    for(let i=0;i<k;i++){
        ans=Math.max(ans,nums[i]);
    }
    anss.push(ans);
    left++;
    right++;
    while(right<nums.length){
        if(nums[left-1]===ans){
            ans=nums[left];
            for(let i=left;i<=right;i++){
               ans=Math.max(ans,nums[i]);
            }
            anss.push(ans);
            left++;
            right++;
        }else{
            ans=Math.max(ans,nums[right]);
            anss.push(ans);
            left++;
            right++;

        }
    }
    return anss;
    
};

该题解时间复杂度为O(KN)超时了,便不再赘述

正解(单调队列)

js 复制代码
/**
 * @param {number[]} nums  // 输入:一个数字数组 nums
 * @param {number} k      // 输入:滑动窗口的大小 k
 * @return {number[]}  // 输出:一个数组,包含每次滑动窗口的最大值
 */
var maxSlidingWindow = function(nums, k) {
    const n = nums.length; // 获取数组 nums 的长度

    // 边界情况处理:
    if (n === 0 || k === 0) return []; // 如果数组为空或者窗口大小为 0,则返回空数组
    if (k === 1) return nums;       // 如果窗口大小为 1,则直接返回原数组(每个元素都是当前窗口的最大值)

    const result = [];  // 初始化结果数组,用于存储每个窗口的最大值
    const deque = [];   // 初始化双端队列 (deque),用于存储窗口内元素的索引。  这个队列是单调递减的。

    // 主循环:遍历数组 nums
    for (let i = 0; i < n; i++) {
        // 1. 移除超出窗口范围的元素:
        // 循环条件:
        // - deque 数组不为空  (deque.length > 0)
        // - 队列头部的索引小于等于 i - k (deque[0] <= i - k)。  这意味着队列头部的元素已经滑出了当前窗口。
        while (deque.length > 0 && deque[0] <= i - k) {
            deque.shift(); // 从队列头部移除元素!  这保证了队列中只包含当前窗口内的元素的索引。
        }

        // 2. 维护队列的单调递减性:
        // 循环条件:
        // - deque 数组不为空 (deque.length > 0)
        // - 队列尾部索引对应的元素值小于等于当前元素值 (nums[deque[deque.length - 1]] <= nums[i]) 。这意味着当前元素比队列尾部的元素更大或相等,那么队列尾部的元素就不可能是窗口的最大值了,所以需要移除。
        while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i]) {
            deque.pop(); // 从队列尾部移除元素! 这保证了队列中的元素是单调递减的,队首元素始终是当前窗口的最大值。
        }

        // 3. 将当前元素的索引添加到队列尾部:
        deque.push(i); // 将当前遍历到的元素的索引 i 添加到队列尾部。

        // 4. 当窗口完全形成时,将最大值添加到结果数组:
        // 条件:i >= k - 1  表示当前窗口已经完全形成,可以计算最大值了。 例如,当k为3时,i=2时,result开始push
        if (i >= k - 1) {
            result.push(nums[deque[0]]); // 将队列头部索引对应的元素值(当前窗口的最大值)添加到 result 数组中。  deque[0] 始终是队列中最大的元素的索引。
        }
    }

    return result; // 返回结果数组
};

核心思想

该解法的核心是使用单调递减队列(deque)来高效地维护滑动窗口中的最大值。 单调队列保证队首元素始终是当前窗口的最大值,从而避免了每次滑动窗口时都进行线性扫描。

详细解析

  1. 初始化:

    • nums:输入数组.
    • k:窗口大小.
    • result:存储结果的数组.
    • deque:双端队列,存储 索引 , 保持队列内的元素 从大到小递减 (单调递减).
  2. 遍历数组:

    for (let i = 0; i < n; i++) 循环遍历 nums 数组。

    • 维护队列:

      • 移除过期元素: while (deque.length > 0 && deque[0] <= i - k)

        • 如果队列头的索引已经超出窗口范围 i - k,则从队列头移除该索引。 目的是保持队列中的索引都在当前窗口内。
      • 保持单调递减: while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i])

        • 如果当前元素 nums[i] 大于等于队列尾部索引对应的元素值nums[deque[deque.length - 1]],则从队列尾部移除索引,直到队列为空或者找到一个比当前元素大的元素。 目的是确保队列是单调递减的,这样队列头部始终是当前窗口的最大值索引。
      • 添加当前元素索引: deque.push(i)

        • 将当前元素的索引 i 添加到队列尾部。
    • 添加到结果集: if (i >= k - 1)

      • 当窗口完全形成(i >= k - 1)之后,将队列头部索引 correspond的值 nums[deque[0]] (当前窗口的最大值) 添加到结果数组 result 中。
  3. 返回结果:return result

四、结语

再见!

相关推荐
盖世英雄酱581361 分钟前
同事说缓存都用redis啊,数据不会丢失!真的吗?
redis·后端·面试
抹茶san7 分钟前
el-tabs频繁切换tab引发的数据渲染混淆
前端·vue.js·element
Captaincc11 分钟前
关于MCP最值得看的一篇:MCP创造者聊MCP的起源、架构优势和未来
前端·mcp
小小小小宇15 分钟前
记录老项目Vue 2使用VueUse
前端
vvilkim15 分钟前
React Server Components 深度解析:下一代 React 渲染模式
前端·react.js·前端框架
HBR666_25 分钟前
vue3 excel文件导入
前端·excel
天天扭码29 分钟前
偶遇天才算法题 | 拼劲全力,无法战胜 😓
前端·算法·面试
小菜刀刀33 分钟前
文件包含漏洞,目录遍历漏洞,CSRF,SSRF
前端·csrf
2401_8786247944 分钟前
opencv(双线性插值原理)
人工智能·算法·计算机视觉
小豪GO!1 小时前
数据结构-八大排序
数据结构·算法·排序算法