题目描述:
给你一个整数数组 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]
思路:
这是一道使用单调队列的经典题目。
暴力的解法为:遍历一遍数组的过程中,每次都从窗口中找到最大的数值。但这样的解法时间复杂度为 O(n × k)。而如果想在线性时间复杂度内解决此题,我们就需要一个特殊的队列。这个特殊的队列在将当前窗口里的元素放进去后,随着窗口的移动,队列也一进一出,而每次移动之后,队列需要告诉我们里面的最大值是什么。而这题使用到的这个特殊队列,就是单调队列
单调队列是一种特殊的队列,它可以在队列的两端进行插入和删除操作,并且保持队列中的元素单调递增或单调递减。在这道题中,我们需要维护一个单调递减的队列,用来存储当前窗口中的元素,并且队列的头部元素就是当前窗口的最大值。然而这个队列并没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
具体实现步骤如下:
使用Python 中内置的双端队列(deque)创建 id 和 val 两个双端队列,分别用于存储元素的下标和元素的值。
处理前 k 个元素时,将第一个元素加入队列,并依次将后面的元素加入队列。同时,维护队列中的元素单调递减,即如果当前元素小于等于队列尾部元素,则将当前元素加入队列;如果当前元素大于队列尾部元素,则弹出队列尾部元素,直到队列为空或当前元素小于等于队列尾部元素,然后将当前元素加入队列。
处理剩余元素时,依次将元素加入队列,并维护队列中的元素单调递减。如果当前元素小于等于队列尾部元素,则将当前元素加入队列;如果当前元素大于队列尾部元素,则弹出队列尾部元素,直到队列为空或当前元素小于等于队列尾部元素。同时,记录当前窗口的起始下标。
处理结果时,将队列中的元素加入结果列表。在每个窗口位置上,如果队列头部元素是当前窗口的起始下标,则弹出队列头部元素,然后将队列头部元素加入结果列表。
代码(python):
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 初始化双端队列
id = deque() # 存储元素下标
val = deque() # 存储元素值
val.append(nums[0]) # 将第一个元素加入队列
id.append(0) # 将第一个元素下标加入队列
# 处理前k个元素
for i in range(1, k):
if nums[i] <= val[-1]: # 如果当前元素小于等于队列尾部元素
id.append(i) # 将当前元素下标加入队列
val.append(nums[i]) # 将当前元素加入队列
else: # 如果当前元素大于队列尾部元素
val.pop() # 弹出队列尾部元素
while val and nums[i] > val[-1]: # 如果队列不为空且当前元素大于队列尾部元素
val.pop() # 弹出队列尾部元素
id.pop() # 弹出队列尾部元素下标
val.append(nums[i]) # 将当前元素加入队列
# 处理剩余元素
result = [] # 存储结果
for i in range(k, len(nums)):
if nums[i] <= val[-1]: # 如果当前元素小于等于队列尾部元素
id.append(i) # 将当前元素下标加入队列
val.append(nums[i]) # 将当前元素加入队列
else: # 如果当前元素大于队列尾部元素
temp = i # 记录当前元素下标
while nums[i] > val[-1]: # 如果当前元素大于队列尾部元素
if i - k + 1 <= id[-1]: # 如果队列头部元素在当前窗口内
val.pop() # 弹出队列尾部元素
temp = id[-1] # 记录队列头部元素下标
id.pop() # 弹出队列头部元素下标
else: # 如果队列头部元素不在当前窗口内
temp = i - k + 1 # 记录当前窗口的起始下标
break
id.append(temp) # 将当前窗口的起始下标加入队列
val.append(nums[i]) # 将当前元素加入队列
# 处理结果
val.appendleft(-1) # 在队列头部加入-1
id.append(len(nums)) # 在队列尾部加入len(nums)
for i in range(len(nums) - k + 1):
if id[0] == i: # 如果队列头部元素是当前窗口的起始下标
val.popleft() # 弹出队列头部元素
id.popleft() # 弹出队列头部元素下标
result.append(val[0]) # 将队列头部元素加入结果列表
return result # 返回结果列表
- 时间复杂度: O(n)
- 空间复杂度: O(k)