滑动窗口最大值 单调队列

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]

示例 3:

输入:nums = [1,-1], k = 1输出:[1,-1]

示例 4:

输入:nums = [9,11], k = 2输出:[11]

示例 5:

输入:nums = [4,-2], k = 2输出:[4]

思路:

  初次看这个题的思路时,应该不认为它是个困难题,毕竟我们的步骤似乎很明确:只要维护一个区间,一边移动一边计算区间内最大值就好了嘛。但实际上,假如我们每移动一小步都要重新计算窗口中的最大值,这个过程是具有极高复杂度的!不去讨论在一个区间内获得最大值的时间复杂度,我们知道每次移动区间,最多就涉及到一个旧元素的减少和一个新元素的增加,那么中间那些元素都是不变的,我们是肯定没有必要再次对他们进行取最大值计算的。

  这道题应该用单调队列来做。单调队列顾名思义也就是一个用来存放递增、递减数的数组。我们随着滑动窗口的移动,只需要维护一个队列即可,遇到新元素往队列里添加、旧元素要淘汰了就把它去除即可。

  这里的单调队列和之前讲过的单调栈差不多,往里添加元素时要遵循“单调”的原则,如果不满足条件就要先删除,无非栈是从顶部删除,队列则是从底部删除罢了。我们用单调队列win来存储递减值的下标(注意,是下标不是值!),win[0]也就是我们当前最大的元素的下标,nums[win[0]]才是当前最大元素。存储下标而不是直接存储值的好处在于:随着我们更新滑动窗口,我们可以通过下标判断当前队列当中的值是不是应该要被删除了(比如窗口已经离这一值而去了,不再覆盖这一值了)

  虽然是一个“窗口”,但我们用一个right指针控制右边界就可以,左边界我们交给对right和win[0]之间的差即可。随着窗口右指针right的前进,具体代码细节一共就三个步骤:

  1. 检查删除过期元素。
  2. 按规则添加新元素到单调队列。
  3. 将单调队列的首位(最大值下标对应的值)添加进结果。

代码:

class Solution(object):

    def maxSlidingWindow(self, nums, k):

        res = [] #res是最终结果

        win=[] #win存储的是下标,递减队列值的下标 即这个下标对应的值是递减的

        lenth = len(nums)

        right=0#right充当滑动窗口的右边界

        while(right<lenth):#在右边界到头之前

            #1.先尝试删除窗口中过期元素

            if right>=k and right-win[0]>=k:#如果right和win[0]差开了 ,win[0]就不能用了

                win.pop(0)

            #2.删除完了 添加 按单调队列规则

            while win and nums[right]>=nums[win[-1]]: #往单调队列里添加元素

                win.pop()

            win.append(right)

            #3.添加完了 当前win[0]是一个答案,放入res

            if right+1>=k:#当right指针到窗口规定大小时,比如k=3时,right到下标2了

                res.append(nums[win[0]])#就可以开始每次移动right时添加结果啦

            right+=1

        return res

小结:

  这道题确实的当之无愧的困难题了。可以看到代码其实是非常抽象的,不管是理解起来还是写起来都是比较难受的。就算理解了具体思路,但实际写的时候也会有很多细节需要注意,尤其是一些条件的判断。其实我为了让这段代码好写点,代码里很多地方我都有意写成“>=k”,尽可能让不同地方统一起来。

  总体来说,重点要记住这一题是用单调队列来解决的,然后要形成自己的逻辑体系——不过这也挺难的,时间久了必然需要再复习~

posted @   JunanP  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示