动态规划-打家劫舍专题

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。

状态的转移方程就是dp[i] = max(dp[i-1], dp[i-2] + nums[i])

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])

        dp = [0] * len(nums)
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])

        for i in range(2, len(nums)):
            dp[i] = max(dp[i-1], dp[i-2] + nums[i])
        return dp[-1]

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])
        a = nums[0]
        b = max(nums[0], nums[1])
        for i in range(2, len(nums)):
            c = max(a+nums[i], b)
            a = b
            b = c
        return c

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额
示例 1:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

这道题与上道题的区别是环形,所以,第一个和最后一个必定不可以一起拿下。~~~一个简单的解决办法就是,因为最后必定第一个和最后一个不可能同时拿下,所以只需要把环形分成两个,分别包含第一个和最后一个。此时就同化为第一道题的形式进行解决,最后比较哪个比较大。

class Solution:
    def rob(self, nums: List[int]) -> int:
        # 第一个房屋和最后一个房屋不可以同时拿下
        # 其他就还是跟第一个形式一样。那么其实就是把第一个去掉和把最后一个去掉,看哪个最大。
        # dp[i]指的是前i间房屋可以得到的最大值
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])

        nums1 = nums[0:-1]
        nums2 = nums[1:]

        dp1 = [0] * len(nums1)
        dp2 = [0] * len(nums2)

        dp1[0] = nums1[0]
        dp1[1] = max(nums1[0], nums1[1])
        
        dp2[0] = nums2[0]
        dp2[1] = max(nums2[0], nums2[1])

        for i in range(2, len(nums)-1):
            dp1[i] = max(dp1[i-1], dp1[i-2]+nums1[i])
            dp2[i] = max(dp2[i-1], dp2[i-2]+nums2[i])
        if dp1[-1] > dp2[-1]:
            return dp1[-1]
        else:
            return dp2[-1]

337. 打家劫舍 III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

二叉树不需要多想,只需要考虑根,左孩子,右孩子;后续就可以灌到所有的case上。本题里根,左孩子,右孩子里必会选择一个。

class Solution:
    import functools
    @functools.lru_cache(None)  # 加上缓存之后就可以通过。
    def rob(self, root: TreeNode) -> int:
        if not root:
            return 0
        money = root.val
        if root.left:
            money = money + self.rob(root.left.left) + self.rob(root.left.right)
        if root.right:
            money = money + self.rob(root.right.left) + self.rob(root.right.right)
        return max(money, self.rob(root.right) + self.rob(root.left)) 


#针对解法一种速度太慢的问题,经过分析其实现,我们发现 爷爷在计算自己能偷多少钱的时候,同时计算了 4 个孙子能偷多少钱,也计算了 2 个儿子能偷多少钱。这样在 儿子当爷爷 时,就会产生重复计算一遍孙子节点。下面是动态规划的关键优化点:

class Solution:
    def rob(self, root: TreeNode) -> int:
        def dfs(node):
            if not node: return 0, 0
            l = dfs(node.left)
            r = dfs(node.right)
            selected = node.val + l[1] + r[1]
            notSelected = max(l[0], l[1]) + max(r[0], r[1])
            return selected, notSelected
        return max(dfs(root))

猜你喜欢

转载自blog.csdn.net/caihuanqia/article/details/112548040