题目
给定一个数组 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 ≤ 输入数组的大小。(但是在数组可以为空,这个时候k会等于0)(LeetCode都是骗人的233)
除了暴力遍历方法,可以通过存储滑动窗口的数据结构入手,使用堆或者双向队列存储滑动窗口内的值。这里选择堆(双向队列貌似更简单更快)。
"每次只找最大值"启发了我使用胜者树来解决问题。
胜者树与败者树
胜者树和败者树都是完全二叉树,是树形选择排序的一种变型。每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛。
不同的是,胜者树的中间结点记录的是胜者的标号;而败者树的中间结点记录的败者的标号。
胜者树与败者树可以在log(n)的时间内找到最值。任何一个叶子结点的值改变后,利用中间结点的信息,还是能够快速地找到最值。在k路归并排序中经常用到。
思路
使用胜者树有两个关键点:第一是如何初始化,第二是当滑动窗口移动时,胜者树如何重构。
初始化
-
首先确定树的实现方法。由于是完全二叉树,可以使用数组(列表)模拟树型结构。
list[1]
为根节点,对于任意节点list[i]
,list[i/2]
为父节点,(python的整数除为//
而不是/
)list[i/2*2]
和list[i/2*2+1]
为左右子节点 -
其次确定树的节点个数(数组的大小)。对于k个要比较的数据,创建一个
2k-1
大小的完全二叉树即可。(注意各个节点对应的数组下标)
重构
-
首先确定新加入的“选手”在树中叶子节点的位置。当滑动窗口移动时,每次替换的叶子节点位置也依次变化。
-
根据胜者树逐层向上比较,最终找到最大值。
实现步骤
- 用数组nums的前k个元素构造胜者树并初始化
- 逐渐向后遍历nums的每个元素,将其加入胜者树进行比较,找到当前滑动窗口的最大值并记录。
python3实现
def maxSlidingWindow(nums, k: int):
result=[]
if not nums:
return result
elif k==1:
return nums
# 确定胜者树的大小
size=2*k
tail=size-1
# 初始化胜者树
tree=[None]*size
for i in range (k):
tree[i+k]=nums[i]
p=tail
while(p!=1):
if tree[p]>tree[p-1]:
tree[p//2]=tree[p]
else:
tree[p//2]=tree[p-1]
p-=2
result.append(tree[1])
# 重建胜者树
extra=0
for index in range(k,len(nums)):
number=nums[index]
# p为替换的"选手"的位置
# 由于滑动窗口向前滑动,每次替换的叶子节点的位置不一样,因此重新计算p
# extra用来决定替换位置,具体替换规律可通过画图观察得到
p=tail-k+1+extra%k
tree[p]=number
while(p!=1):
parent=p//2
l_child=parent*2
r_child=l_child+1
if tree[l_child]>tree[r_child]:
tree[parent]=tree[l_child]
else:
tree[parent]=tree[r_child]
p=parent
result.append(tree[1])
extra+=1
return result
num=[1,3,-1,-3,5,3,6,7]
print(maxSlidingWindow(num,3))
心得体会
本题借用了胜者树败者树的思路,但是跟“外部排序”略有不同,只能使用胜者树,不能使用败者树,因为排序的时候是找到最大(最小)值后就将根节点的值弹出不再使用(即每次重构根节点的值都会更新),而本题最大(最小)值可能会重复使用(即每次重构根节点的值不一定会更新)。败者树只存储了败者,重构时一定会更新根节点,所以会出现问题。
另外当滑动窗口移动时,重构时每次替换的叶子节点位置也依次变化,如果没注意这点,也会导致bug。