题目
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
示例 1:
输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7
示例 2:
输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9
提示:
树的节点数在 [1, 104] 范围内
0 <= Node.val <= 104
思路和代码
这题已经刷了好几遍了,今晚再看,好家伙,还是两眼一抹黑。题解里写了三种方法,暴力、记忆化搜索和动态规划,好家伙,暴力的都不会写。
1.暴力解法
暴力的话就是分两种情况,这个结点是偷还是不偷。偷的话就要考虑该结点的左右孩子的左右孩子节点,不偷的话就可以考虑该结点的左右孩子结点。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
#暴力
if root is None:
return 0
if root.left is None and root.right is None:
return root.val
val1 = root.val
#偷该节点
if root.left:
val1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
val1 += self.rob(root.right.left) + self.rob(root.right.right)
#不偷该节点
val2 = self.rob(root.left) + self.rob(root.right)
return max(val1, val2)
树的递归我写了不少题目,自己写还是写不出来。递归三要素,传的参数和返回值、终止条件和单层递归逻辑。(递归三要素和动规五要素背的滚瓜烂熟又怎么样呢?还是不会写)
2.记忆化递归
这个有点新奇,其实也不是特别理解,代码也没这样写过。意思就是利用字典把计算的结果保存下来,这样计算过孙子,再计算孩子的时候可以直接利用孙子的信息。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
#不同处1
memory = {}
def rob(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
if not root.left and not root.right:
return root.val
#不同处2
if self.memory.get(root) is not None:
return self.memory[root]
val1 = root.val
if root.left:
val1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
val1 += self.rob(root.right.left) + self.rob(root.right.right)
val2 = self.rob(root.left) + self.rob(root.right)
#不同处3
self.memory[root] = max(val1,val2)
return max(val1, val2)
时间复杂度从O(n2)降到O(n),空间复杂度还是O(logn).
3.动态规划
利用长度为2的数组记录偷与不偷的最大金额。直接上代码吧。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
ans = self.rob_tree(root)
return max(ans)
def rob_tree(self, root):
if not root:
#(偷该结点的最大金额,不偷该结点的最大金额)
return (0,0)
left = self.rob_tree(root.left)
right = self.rob_tree(root.right)
#偷当前结点
val1 = root.val + left[1] + right[1]
#不偷当前节点
val2 = max(left) + max(right)
return (val1, val2)
2022.11.21再刷
还是写了三种方法,暴力+记忆化递归+动态规划,前两个写的比较顺利,后一个没有完全写出来。
其中对记忆化递归有了更深的理解,memory就是一个字典,把已经算过的结果保存下来。之所以要写在函数外面,因为如果写在函数里面,递归的时候就把保存的内容清零了。此外,加了这个之后,一个是在递归前判断一下这个值有没有算过保存下来了。其次,再算新的结果的时候加到这个memory中。
代码:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
memory = {}
def rob(self, root: Optional[TreeNode]) -> int:
#三种方法 暴力 记忆化递归 动态规划
#都是分为两种情况 偷或者不偷
if not root:
return 0
if root in self.memory:
return self.memory[root]
#偷当前节点和当前节点的孙子节点
val1 = root.val
if root.left:
val1 += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
val1 += self.rob(root.right.left) + self.rob(root.right.right)
#不偷当前节点 偷孩子节点
val2 = self.rob(root.left) + self.rob(root.right)
self.memory[root] = max(val1, val2)
return max(val1, val2)
这与原来的代码相比除了memory在判断是否已经计算过外有点点区别外(题解里用的是get方法),还有就是有一种情况没有考虑,就是左右没有孩子节点,返回该值。不过这个没写好像并不影响吧。
至于动态规划,其实我看我写的和题解里的思路是差不多的,但就是超时了。我比较了一下代码,觉得可能按我的代码执行,每次会多执行两次递归?其实不是很懂。
代码:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
ans = self.rob_tree(root)
return max(ans)
def rob_tree(self, root):
if not root:
#左边表示偷 右边表示不偷
return [0,0]
left = self.rob_tree(root.left)
right = self.rob_tree(root.right)
#偷当前节点
# val1 = root.val
# val1 += self.rob_tree(root.left)[1] + self.rob_tree(root.right)[1]
val1 = left[1] + right[1]+ root.val
#不偷当前节点
# val2 = max(self.rob_tree(root.left)) + max(self.rob_tree(root.right))
val2 = max(left) + max(right)
return [val1,val2]