leetcode337. 打家劫舍 IIIpython-动态规划篇

题目
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 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]

你可能感兴趣的:(动态规划,算法)