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])
實現結果
歡迎關注
公衆號 【書所集錄】