算法:
此时我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。
单调队列:维护元素单调递减的队列,即单调递减或单调递增的队列。
由于我们需要求出的是滑动窗口的最大值,如果当前的滑动窗口中有两个下标 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
-
初始化:创建空的双端队列
dq
和结果列表res
。 -
遍历数组:
-
第一个元素:
1
,加入队列dq = [0]
。 -
第二个元素:
3
,移除比3
小的元素1
,dq = [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