題目
給定一個數組 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。