题目描述
这是一道力扣上面的题目: leetcode.cn/problems/ho…
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
视频题解:www.bilibili.com/video/BV1j2…
文字题解
一、定义状态
遇到这种情况,一般都从最简单的情况开始分析。
根据题目,对于下面这种结构的树,对于父节点G而言,我们可以有两种偷取方式:
第一种方式
父节点G加上左右子节点的子节点(孙子节点),即
robmoney1 = sum(节点G + 节点A + 节点B + 节点C + 节点D)
第二种方式
抢左右子节点,即 robmoney2 = sum(节点E + 节点F)
然后计算这两种偷取方式的最大值:max(robmoney1,robmoney2)
二、状态转移
这明显就是有规则的遍历,对于树的题目,涉及到遍历,一般都可以用递归来解决
于是可以写出以下代码:
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution(object):
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
# 第一种方式:偷父节点加上父节点的左右孙子节点
robMoney1 = root.val
if root.left:
robMoney1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
robMoney1 += self.rob(root.right.left) + self.rob(root.right.right)
# 第二种方式:偷父节点的子节点
robMoney2 = self.rob(root.left)+self.rob(root.right)
# 取两种方式的最大值
return max(robMoney1, robMoney2)
递归最大的问题就是有很多重复计算,这是因为在这个过程中,会从根节点开始向下递归,先计算子树对应的状态,再往上计算父节点对应的状态,直到计算完整棵树。
避免这种问题可以把每次偷过相同的节点都记录下来,所以对于下面这种类型的树
记录表格是这样的:
可以加一个哈希表来记录,于是代码又变成了这个样子:
# Definition for a binary tree node.
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution(object):
def __init__(self):
self.memoMoney = {}
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
# 如果这个节点先前已经计算过,直接用记录的结果,避免重复计算
if self.memoMoney.get(root):
return self.memoMoney[root]
# 第一种方式:偷父节点加上父节点的左右孙子节点
robMoney1 = root.val
if root.left:
robMoney1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
robMoney1 += self.rob(root.right.left) + self.rob(root.right.right)
# 第二种方式:偷父节点的子节点
robMoney2 = self.rob(root.left)+self.rob(root.right)
# 取两种方式的最大值
self.memoMoney[root] = max(robMoney1, robMoney2)
return self.memoMoney[root]
这种解法的时间复杂度为O(n),空间复杂度为O(n)。
总结
这其实是一道动态规划的题目,树形DP的主要思想,就是将一个大问题分解成若干个小问题进行求解,并将它们的结果合并起来得到最终答案。
在树形DP中,我们通常以树的节点作为子问题的状态,然后采用递归或者记忆化搜索的方式进行求解。
对于这种树形DP的题目,一般步骤如下:
-
定义状态:将每个节点定义为一个状态,然后找到合适的状态转移方程式。
-
状态转移:通过递归或记忆化搜索的方式进行状态转移,计算出每个节点的最优解。
-
合并答案:对于每个子问题的解,通过合适的方式进行合并,得到整个树的最终答案。
树形DP常用的技巧包括:记忆化搜索、递归等等。
补充
如果你不太明白动态规划是什么,可以看下这个题解:
青蛙跳台阶 (题解)