打败算法 —— 滑动窗口最大值
本文参考
出自LeetCode上的题库 —— 滑动窗口最大值,一般的双指针解法会导致时间超时,需要借助大根堆(大顶堆)实现
https://leetcode-cn.com/problems/sliding-window-maximum/
滑动窗口最大值问题
给定一个整数数组nums,有一个大小为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
解题思路
第一种解法是直接利用双指针,让$left$和$right$指针每次向右移动一个位置,每次移动后求取窗口内子串的最大值。但这种直观的思路会导致时间超时,若数组的长度为$n$,窗口长度为$k$,时间复杂度为$O(nk)$
第二种解法是借助python的heapq小根堆函数库,如果需要大根堆的效果,只需对数组的每个值取负值。创建堆的时间复杂度为$O(n)$,修改堆的时间复杂度为$O(logn)$,这时可能会问,我每次从堆里删除不在窗口中的元素,再向堆里插入窗口中新增的元素,还是很耗时啊?实际上,我们不需要在窗口移动时,每次都删除堆中不需要的元素,只要保证堆顶的元素在窗口中就行,从而减少堆的pop操作
双指针解法(超时)
class Solution:
def max_sliding_window(self, nums: List[int], k: int) -> List[int]:
left = 0
right = k
ans = list()
while right <= len(nums):
curr = max(nums[left:right])
ans.append(curr)
left += 1
right += 1
return ans
大根堆解法(nlogn)
def max_sliding_window(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# 默认是小根堆,添加负号变成大根堆
q = [(-nums[i], i) for i in range(k)]
heapq.heapify(q)
ans = [-q[0][0]]
for i in range(k, n):
heapq.heappush(q, (-nums[i], i))
# 不断地移除堆顶的元素,直到其确实出现在滑动窗口中
while q[0][1] <= i - k:
heapq.heappop(q)
ans.append(-q[0][0])
return ans