【Leetcode堆和双端队列】滑动窗口最大值(239)
题目
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例:
输入: 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
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
进阶:
你能在线性时间复杂度内解决此题吗?
解答
方法一:暴力破解。cur表示窗口,记录最大值,依次后移。时间复杂度O(n·k),timeout
方法二:大顶堆。Time: O(N*logN), Space: O(N),timeout
思路:元素和下标依次入堆,每次获取窗口最大值时,检查堆顶元素的下标在不在窗口内,不在就pop掉,不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。
PS:把元素组合(x, y)入堆,可以保持堆特性。
方法三:双端队列,时间复杂度O(n), 空间复杂度O(k)
思路:依次遍历列表中的元素,cur存放已经在窗口中的元素的下标,假如当前元素为c, 则用c和cur中元素从后向前比较,删除cur中小于c的元素,并把c的下标加到cur中,这步操作保证了cur[0]表示的元素永远是窗口中最大的
通过代码如下:
# 1. 爆破 timeout Time: O(N*K), Space: O(1)
# 2. 用大顶堆 Time: O(N*logN), Space: O(N)
# 3. 双端队列 Time: O(N), Space: O(K)
# class Solution:
# # 爆破
# def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# if not nums: return []
# ans = []
# length = len(nums)
# for i in range(length-k+1):
# ans.append(max(nums[i: i+k]))
# return ans
# class Solution:
# # 大顶堆
# from heapq import heappush, heappop, heapify
# def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# if not nums: return []
# length = len(nums)
# q = [(-nums[i], i) for i in range(k)] # 下标也入堆,用于判断元素在不在窗口内
# heapify(q)
# ans = [-q[0][0]]
# for i in range(k, length):
# heappush(q, (-nums[i], i))
# while q[0][1] <= i-k: # 堆顶在不在窗口,不在则删掉
# heappop(q) # log(n)
# ans.append(-q[0][0])
# return ans
from collections import deque
class Solution:
# 双端队列
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums: return []
ans = []
win = deque([]) # 存数值下标
for i, x in enumerate(nums):
# 窗口满了,删除最左侧。(win保存下标,不然删除最左侧 没法删,用下标和win第一个做比较)
if win and i - win[0] >= k:
win.popleft()
# 进窗口之前,删除window小于x的,(x在 他们没有出头之日)
while win and x >= nums[win[-1]]:
win.pop()
# x加入窗口
win.append(i)
# 保存window max
if i+1 >= k:
ans.append(nums[win[0]])
return ans