1. 程式人生 > 實用技巧 >LeetCode 337. 打家劫舍 III | Python

LeetCode 337. 打家劫舍 III | Python

337. 打家劫舍 III


題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/house-robber-iii

題目


在上次打劫完一條街道之後和一圈房屋後,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之為“根”。 除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之後,聰明的小偷意識到“這個地方的所有房屋的排列類似於一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。

計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。

示例 1:

輸入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

輸出: 7 
解釋: 小偷一晚能夠盜取的最高金額 = 3 + 3 + 1 = 7.

示例 2:

輸入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1

輸出: 9
解釋: 小偷一晚能夠盜取的最高金額 = 4 + 5 = 9.

解題思路


這道題是 【198. 打家劫舍】、【213. 打家劫舍 II】 的後續

思路:遞迴、動態規劃

在這道題當中,偷竊的物件不再是一條街或者圍成圈的房屋。而是看似二叉樹排布的房屋。在這裡,我們會用動態規劃的方法來解決。

不過在此之前,我們先看題目,【如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警】,這裡的意思是,不能夠偷竊相連的兩個節點,也就是不能同時偷竊屬於父子關係的節點。

題目給定的二叉樹中,節點上的權值也就是要偷竊的金額,按照上面的條件,也就是說,每個節點都有兩種狀態:已偷取、未偷取。由於不能同時偷竊屬於父子關係的節點,求該條件限定下,能偷取的金額最大值為多少。

遞迴

在這裡,我們先用遞迴的方法來解決,這裡根據上面的分析,分情況來討論,具體思路如下(基於三層的二叉樹描述):

  • 當偷竊根節點時,則無法偷竊根節點下的左右子節點,但是可以選擇投左右子節點下的子樹。
  • 當不偷竊根節點時,則可以考慮偷竊根節點下的左右子節點。

那麼,大致的程式碼如下:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def rob(self, root: TreeNode) -> int:
        if not root:
            return 0
        # 分情況
        # 偷竊根節點 與 不偷竊根節點
        in_root = root.val
        if root.left:
            # 偷竊根節點時,無法偷取子節點,那麼偷竊子孫節點
            in_root += self.rob(root.left.left) +self. rob(root.left.right)
        if root.right:
            in_root += self.rob(root.right.left) +self. rob(root.right.right)
        
        # 不偷取根節點,那麼收益就是左右子節點權值的總和
        not_in_root = self.rob(root.left) + self.rob(root.right)

        # 返回大值
        return max(in_root, not_in_root)

上面這段程式碼執行後超時,因為在計運算元孫節點的時候,計算了根節點下的左右子樹,而後續計算又重複計算了子孫節點。在這裡,我們來進行優化,將計算後的結果儲存到雜湊表中,遇到需要重複計算的部分,直接拿過來,不需要再次遞迴計算。

優化後代碼見【程式碼實現 # 遞迴(優化)】

動態規劃

從上面遞迴的方法中,我們可以發現,最終最大收益為 in_root 和 not_in_root 的最大值。

也就是說,每個子樹都有最優解:偷竊根節點 和 不偷竊根節點下的最優解。

我們重新定義這個問題,每個節點可以選擇偷或者不偷,那麼相連節點不能一起偷,那麼:

  • 如果當前節點選擇偷竊時,左右子節點不選擇偷;
  • 如果當前節點選擇不偷時,左右子節點主要能獲得最優解就行。

定義陣列儲存兩個狀態,索引 0 表示不偷,索引 1 表示偷。那麼每個節點能偷到最大金額可定義為:

  • 當前節點選擇偷竊時,最大金額數 = 左子節點不偷能獲得的最大金額 + 右子節點不偷能獲得的最大金額 + 當前節點的金額
  • 當前節點選擇不偷時,最大金額 = 左子節點能偷的最大金額 + 右子節點能偷的最大金額。

那麼相應的轉移公式為:

# 當前節點不偷
ans[0] = max(rob(root.left)[0], rob(root.left)[1])
       + max(rob(root.right)[0], rob(root.right)[1])
# 當前節點選擇偷
ans[1] = rob(root.left)[0] + rob(root.right)[0] +  root.val

具體程式碼見【程式碼實現 # 動態規劃】

程式碼實現


# 遞迴(優化)
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def rob(self, root: TreeNode) -> int:
        # 儲存計算過的結果
        hash_map = {}
        def helper(root):
            if not root:
                return 0
            # 如果存在於雜湊表中,直接拿過來用
            if root in hash_map:
                return hash_map[root]
            in_root = root.val
            if root.left:
                # 偷竊根節點時,無法偷取子節點,那麼偷竊子孫節點
                in_root += helper(root.left.left) +helper(root.left.right)
            if root.right:
                in_root += helper(root.right.left) +helper(root.right.right)

            # 不偷取根節點,那麼收益就是左右子節點權值的總和
            not_in_root = helper(root.left) + helper(root.right)
            ans = max(in_root, not_in_root)
            # 這裡計算完之後,將結果存入雜湊表中
            hash_map[root] = ans
            return ans
        return helper(root)

# 動態規劃
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def rob(self, root: TreeNode) -> int:
        def helper(root):
            if not root:
                return [0, 0]
            
            ans = [0, 0]

            left = helper(root.left)
            right = helper(root.right)

            # 兩個索引對應兩種狀態,索引 0 表示不偷,索引 1 表示偷
            ans[0] = max(left[0], left[1]) + max(right[0], right[1])
            ans[1] = left[0] + right[0] + root.val

            return ans
        
        ans = helper(root)
        return max(ans[0], ans[1])

實現結果


歡迎關注


公眾號 【書所集錄