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
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
Related Topics 堆 Sliding Window
提到滑动窗口,首先便想到了滑动窗口算法,所以我们第一步便尝试使用滑动窗口来解决这个问题。
首先回顾下滑动窗口算法框架:
left, right = 0, 0
windown = []
while right < len(nums):
# 增大窗口
windown.append(nums[right])
right += 1
while (windown need to shrink):
windown.remove(nums[left])
left += 1
根据此框架,不难写出如下算法:
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
left, right = 0, k-1
res = []
while right < n:
right += 1
if right - left > k:
left += 1
maxValue = float('-inf')
for i in range(left, right):
maxValue = max(maxValue, nums[i])
res.append(maxValue)
return res
算法复杂度为 O(nk),提交显示超时。我们的重复计算主要发生在内循环,计算窗口内的最大值处。我们尝试对这个过程进行优化。我们知道大顶堆的堆顶即是最大值,那我们是不是可以将窗口内的数值维护成一个大顶堆呢?这样每次我们只需要获得堆顶元素的值。维护一个大小为
nums[left]
如果有重复元素呢?其实我们可以将每个元素及其索引一同存进堆中,这样只要当堆顶元素的索引和当前数据的索引差值大于等于 import heapq # 小顶堆
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
heap = []
res = []
for i in range(n):
while heap and i - heapq.nsmallest(1, heap)[0][1] >= k:
heapq.heappop(heap)
heapq.heappush(heap, [-nums[i], i])
if i+1 >= k:
num, index = heapq.nsmallest(1, heap)[0]
res.append(-num)
return res
算法复杂度为 O(nlogk),提交依然显示超时。那我们还能将复杂度优化到
我们仔细分析上面用堆实现的算法,其实我们关心的只是堆顶元素。只有当堆中元素需要移除时,我们才需要重新堆化:将下一个最大值放到堆顶位置。于是我们可以构建一个有序队列,第1个位置适中保持当前窗口的最大值,当需要加入新值时,第一个值和当前值之间的比当前值小的元素均可以舍弃,因为此时的新值是能在窗口中保持最长时间的值。什么时候需要移除最大值呢——当最大值应当滑出窗口的时候。所以最后我们采用一个记录索引的列表
indexs
:
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
indexs = []
res = []
for i in range(n):
if indexs and i-indexs[0] >= k:
indexs.pop(0)
while indexs and nums[indexs[-1]] < nums[i]:
indexs.pop()
indexs.append(i)
if i >= k-1:
res.append(nums[indexs[0]])
return res
因为每个元素只有一次加入和一次移除操作,所以时间复杂度为