5.7 滑动窗口最大值(LC239-H,单调对列)

算法:

此时我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。

单调队列:维护元素单调递减的队列,即单调递减或单调递增的队列。

由于我们需要求出的是滑动窗口的最大值,如果当前的滑动窗口中有两个下标 i 和 j,其中 i 在 j 的左侧(i<j),并且 i 对应的元素不大于 j 对应的元素(nums[i]≤nums[j]),那么会发生什么呢?

当滑动窗口向右移动时,只要 i 还在窗口中,那么 j 一定也还在窗口中,这是 i 在 j 的左侧所保证的。因此,由于 nums[j] 的存在,nums[i] 一定不会是滑动窗口中的最大值了,我们可以将 nums[i] 永久地移除。

因此我们可以使用一个队列存储所有还没有被移除的下标。在队列中,这些下标按照从小到大的顺序被存储,并且它们在数组 nums 中对应的值是严格单调递减的。因为如果队列中有两个相邻的下标,它们对应的值相等或者递增,那么令前者为 i,后者为 j,就对应了上面所说的情况,即 nums[i] 会被移除,这就产生了矛盾。

当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。

由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。

为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。

​​​​​​​

  • 当窗口滑动时

    • 如果队列里有元素,但它们已经滑出当前窗口范围,我们就把它们移除。
    • 如果队列里有元素比当前元素小,我们把这些元素移除,因为它们不可能再成为最大值。
  • 每次窗口形成后(即 i >= k - 1),我们把队列的第一个元素(即最大值)加入结果。

举例理解:

输入:nums = [1, 3, -1, -3, 5, 3, 6, 7]k = 3
  1. 初始化:创建空的双端队列 dq 和结果列表 res

  2. 遍历数组

    • 第一个元素1,加入队列 dq = [0]

    • 第二个元素3,移除比 3 小的元素 1dq = [1]

    • 第三个元素-1,直接加入队列,dq = [1, 2]

      • 窗口形成i >= 2),最大值是 nums[1] = 3,加入结果:res = [3]
    • 第四个元素-3,加入队列,dq = [1, 2, 3]

      • 最大值仍然是 3,加入结果:res = [3, 3]
    • 第五个元素5,移除所有比 5 小的元素,dq = [4]

      • 最大值是 5,加入结果:res = [3, 3, 5]
    • 第六个元素3,加入队列,dq = [4, 5]

      • 最大值仍然是 5,加入结果:res = [3, 3, 5, 5]
    • 第七个元素6,移除所有比 6 小的元素,dq = [6]

      • 最大值是 6,加入结果:res = [3, 3, 5, 5, 6]
    • 第八个元素7,移除所有比 7 小的元素,dq = [7]

      • 最大值是 7,加入结果:res = [3, 3, 5, 5, 6, 7]

正确代码:

我自己没写这个代码。。。(看到hard就放弃了)

from collections import deque
class Solution(object):
    def maxSlidingWindow(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        n = len(nums)
        q = collections.deque()
        for i in range(k):
            while q and nums[i] >= nums[q[-1]]:
                q.pop()
            q.append(i)

        ans = [nums[q[0]]]
        for i in range(k, n):
            while q and nums[i] >= nums[q[-1]]:
                q.pop()
            q.append(i)
            while q[0] <= i - k:
                q.popleft()
            ans.append(nums[q[0]])
        
        return ans

时间空间复杂度:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值