python3實現最滑動窗口最大值算法題——使用勝者樹


題目

給定一個數組 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大小的完全二叉樹即可。(注意各個節點對應的數組下標)

    勝者樹_1

重構

  • 首先確定新加入的“選手”在樹中葉子節點的位置。當滑動窗口移動時,每次替換的葉子節點位置也依次變化。

  • 根據勝者樹逐層向上比較,最終找到最大值。

實現步驟

  1. 用數組nums的前k個元素構造勝者樹並初始化
  2. 逐漸向后遍歷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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM