前端算法必刷题系列[57]

1,216 阅读3分钟

这是我参与更文挑战的第 2 天,活动详情查看 更文挑战

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

109. 滑动窗口最大值 (sliding-window-maximum)

标签

  • Array
  • 滑动窗口
  • 困难

题目

leetcode 传送门

这里不贴题了,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]

基本思路

双端队列 就是既可以从队头出队,也可以从队尾出队的队列。

为什么要使用双端队列,怎么思考出用双端队列这个方法,我之后会来补充我的思考,因为现在我还无法准确表达,不乱说。

写法实现

var maxSlidingWindow = function(nums, k) {
    // windowIdxArr 顾名思义存 窗口中的下标位置,res 存最终结果
    let [windowIdxArr, res] = [[], []]
    for (let i = 0; i < nums.length; i++) {
        // 当下标的间距范围超出了 k 就把窗口最左边的元素(windowIdxArr[0] = 0)出队
        // [1  3  -1 -3] 5  3  6  7 比如这样 遍历到 -3 (i = 3)了那么 windowIdxArr[0] === i - k 
        // 就要把第一个位置下标0从 windowIdxArr 队头出队 => 1 [3  -1 -3] 5  3  6  7
        if (i >= k && windowIdxArr[0] <= i - k) {
            // ---> [关键(1)]
            windowIdxArr.shift()
        }
        // 重点就是这里 向前依次比较队尾的元素,比他小的从从队尾出, 让队列坐标对应元素单调减
        while (windowIdxArr.length && nums[windowIdxArr.slice(-1)] <= nums[i]) {
            // ---> [关键(2)]
            windowIdxArr.pop()
        }
        // 清除完不符合大小条件的元素后,新加入的元素进入窗口
        // ---> [关键(3)]
        windowIdxArr.push(i)
        // 把本轮窗口中最大的进结果数组
        // 注意至少得3个元素,下标从0开始
        if (i >= k - 1) { 
            // ---> [关键(4)]
            // 因为队列元素是单调减的,本轮最大就是 idx 的第一个对应元素
            res.push(nums[windowIdxArr[0]])
        }
    }
    return res
};

let nums = [1,3,-1,-3,5,3,6,7], k = 3
console.log(maxSlidingWindow(nums, k))

我们跟随代码看下面解析,关键在 [1]/[2]/[3]/[4] 这几个地方

滑动窗口的位置                本轮最大值 res[i 轮最大 idx]
---------------               -----
[1  3  -1] -3  5  3  6  7       3
  • i = 0, 只会执行 关键(3) (其他都不满足) 当前 windowIdxArr (下面简称index 队列)为 [0]
  • i = 1, 此时我们发现 关键(2) 条件满足,(3 大于 队尾元素 1),那么把队尾元素出队,index 队列 为 [], 然后在 把当前 3的下标 1 入队 当前index 队列为 [1],此时 (i(1) < (k长(3) - 1) = 2) 还没到 k 的长度,所以 res 还不需要推入
  • i = 2, 满足[3],入队 index 队列 为 [1, 2], 满足[4],入结果数组 [3]
  • ... 后面依次类推

最核心思想是下面代码关键(2)处: 解释下,当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。这样保证了,队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是本轮滑动窗口中的最大值

另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考