给你一个整数数组 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
提示:
1 <= nums.length <= 105-104 <= nums[i] <= 1041 <= k <= nums.length
单调队列,右边进,左边出,同时记录/维护答案
首先,为了方便记录队首离开窗口,队列中记录的应为元素的下标
怎么维护单调性?如果队列的最后一个元素,在数组中对应的值不大于当前遍历到的数组元素的值,那么就一直将队列中的元素从队尾弹出,这样就确保了队列是单调递减的(更准确地说,是队列元素在数组中的对应位置的值是单调递减的),也就是说,滑动窗口的最大值对应的下标一定是队首。例如示例一中滑动窗口第二个位置,即窗口元素为 [3, -1, 3] 时,此时下一个元素是 5,那么遍历到 5 时,发现它一直比单调队列的最后一个元素对应的值要大,所以会清空单调队列,把 5 对应的下标 4 放入队列中。可以类比为,新来了一个战斗力更强且更年轻的员工,那么就要把过去的战斗力没那么强或者一样强但更老的员工优化掉,这就是"不大于"而非"小于"的原因
那如果这个最大值移出了滑动窗口怎么办?我们只需要在每次遍历的时候记录当前滑动窗口的左端点 left,比较 q[0] 和 left 的大小,如果 q[0] < left ,那么说明这个最大值移出了窗口,直接 popleft(),也就是说只有那个最大值移出了窗口,我们才会管滑动窗口左端点的元素是否移出,如果这个元素不是最大值,那么它移出与否并不需要关心
python
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
ans = [0] * (len(nums) - k + 1)
q = deque() # 队列
for i, v in enumerate(nums):
# 当队尾元素不大于新遍历到的元素的值(老员工不比新员工更强),就出队
while q and nums[q[-1]] <= v:
q.pop()
q.append(i)
# 考虑最老的员工被裁的问题,即左端点出窗口
left = i - k + 1 # 滑动窗口的左端点
if q[0] < left:
# 如果实力最强的员工已经达到裁员年龄(移出滑动窗口),就弹出
q.popleft()
# 将此时滑动窗口的最大值记录到答案中
if left >= 0:
# 如果还没形成完整的、长为 k 的滑动窗口,就先不记录答案,这也是为什么一开始不能令 ans 为 [] 的原因
ans[left] = nums[q[0]]
return ans
input.txt:
3 1 3 -1 -3 5 3 6 7
其中第一个元素表示 k,后面的元素均为 nums 元素
python
from typing import List
from collections import deque
def maxSlidingWindow(nums: List[int], k: int) -> List[int]:
ans = [0] * (len(nums) - k + 1)
q = deque()
for i, v in enumerate(nums):
while q and nums[q[-1]] <= v:
q.pop()
q.append(i)
left = i - k + 1
if q[0] < left:
q.popleft()
if left >= 0:
ans[left] = nums[q[0]]
return ans
def main():
with open("input.txt", "r") as f:
data = f.read().split()
k = int(data[0])
nums = list(map(int, data[1:]))
print(maxSlidingWindow(nums, k))
if __name__ == "__main__":
main()