1.题目描述
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 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.
2.解题思路
本题作为 『198 打家劫舍I』和 『231 打家劫舍II』的进阶版,建议练习本题的同学尝试着先完成前面两题。
-
和大多数同学一样,刚看到此题时我内心不禁暗自窃喜:哈哈哈,有了之前的踩坑经验,完成这到题目TM不是太简单了?
-
废话不多说,先对该二叉树进行层序遍历,记录每层所有元素的和,然后对该数组进行之前 『198 打家劫舍I』 的递归求解。
-
然而情况并非如此,直到我遇到了 [2,1,3,null,4] 这样的测试用例(有多少人是死在这步的,挥动手中的荧光棒让我看到你们):题意并非如隔层求解和那么简单。原因在于对于相邻的两层节点,第一层右边的节点和第二层左边的节点完全可以求和的(此时我的内心是奔溃的😭)。
经过仔细分析(手动严肃脸),正确的解题思路大致是这样的:
-
对于一个以 node 为根节点的二叉树而言,如果尝试偷取 node 节点,那么势必不能偷取其左右子节点,然后继续尝试偷取其左右子节点的左右子节点。
-
如果不偷取该节点,那么只能尝试偷取其左右子节点。
-
比较两种方式的结果,谁大取谁。
由此得到一个递归函数(务必注意该函数的定义!!!):
参考:https://leetcode-cn.com/problems/house-robber-iii/comments/20026
3.代码实现
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
# 尝试对以 root 为根节点的二叉树进行偷取,返回能偷取的最大值
def dfs(self,root,vis):
if not root:
return 0
if root in vis:
return vis[root]
res1 = 0
res2 = 0
# 偷取该节点
res1 += root.val
if root.left:
res1+=self.dfs(root.left.left,vis)+self.dfs(root.left.right,vis)
if root.right:
res1+=self.dfs(root.right.left,vis)+self.dfs(root.right.right,vis)
# 不偷取该节点
res2 = self.dfs(root.left,vis) + self.dfs(root.right,vis)
vis[root] = max(res1,res2)
return vis[root]
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
vis = {}
"""
这种方法重复计算了很多地方,比如要完成一个节点的计算,就得一直找左右子节点计算,
可以把已经算过的节点用 HashMap 保存起来,以后递归调用的时候,先在 HashMap 里找,如果存在直接返回,
如果不存在,等计算出来后,保存到 HashMap 中再返回,这样方便以后再调用。
"""
return self.dfs(root,vis)