LeetCode 337. 打家劫舍 III | Python

337. 打家劫舍 III


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

題目


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

計算在不觸動警報的狀況下,小偷一晚可以盜取的最高金額。數組

示例 1:bash

輸入: [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】 的後續url

思路:遞歸、動態規劃

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

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

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

遞歸

在這裏,咱們先用遞歸的方法來解決,這裏根據上面的分析,分狀況來討論,具體思路以下(基於三層的二叉樹描述):blog

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

那麼,大體的代碼以下:

# 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])

實現結果


實現結果 | 遞歸(優化)

實現結果 | 動態規劃

歡迎關注


公衆號 【書所集錄

相關文章
相關標籤/搜索