动态规划之打家劫舍问题

LeetCode 198 House Robber I

动态规划之打家劫舍问题_第1张图片
假想你就是这个专业强盗,从左到右走过这一排房子,在每间房子前都有两种选择:抢或者不抢。
如果你抢了这间房子,那么你肯定不能抢相邻的下一间房子了,只能从下下间房子开始做选择。
如果你不抢这件房子,那么你可以走到下一间房子前,继续做选择。
当你走过了最后一间房子后,你就没得抢了,能抢到的钱显然是 0(base case)。
以上的逻辑很简单吧,其实已经明确了「状态」和「选择」:你面前房子的索引就是状态,抢和不抢就是选择。
在两个选择中,每次都选更大的结果,最后得到的就是最多能抢到的 money:
dp数组含义:当前有n个房子,能偷到最大的money

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        if n == 1:
            return nums[0]
        dp = [0] * n
        dp[0] = nums[0] # 只有一个房子,偷
        dp[1] = max(nums[0], nums[1]) # 只有两个房子,偷最大的
        for i in range(2, n):
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) # 偷当前房子和前面的前面的房子或者偷前一个房子
        return dp[-1]

LeetCode 213 House Robber II

这道题目和第一道描述基本一样,强盗依然不能抢劫相邻的房子,输入依然是一个数组,但是告诉你这些房子不是一排,而是围成了一个圈。

也就是说,现在第一间房子和最后一间房子也相当于是相邻的,不能同时抢。比如说输入数组 nums=[2,3,2],算法返回的结果应该是 3 而不是 4,因为开头和结尾不能同时被抢。

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)
        nums1 = nums[:-1]
        nums2 = nums[1:]
        dp1 = [0] * len(nums1)
        dp2 = [0] * len(nums2)
        dp1[0] = nums1[0]
        dp1[1] = max(nums1[:2])
        dp2[0] = nums2[0]
        dp2[1] = max(nums2[:2])
        for i in range(2, len(nums1)):
            dp1[i] = max(dp1[i - 2] + nums1[i], dp1[i - 1])
        for i in range(2, len(nums2)):
            dp2[i] = max(dp2[i - 2] + nums2[i], dp2[i - 1])
        return max(dp1[-1], dp2[-1])

LeetCode 337 House Robber III

第三题又想法设法地变花样了,此强盗发现现在面对的房子不是一排,不是一圈,而是一棵二叉树!房子在二叉树的节点上,相连的两个房子不能同时被抢劫,果然是传说中的高智商犯罪:
动态规划之打家劫舍问题_第2张图片
解析:一种情况,偷根节点和根节点的左子树的左右子树和根节点的右子树的左右子树,另一种情况,不偷根节点,偷根节点的左右子树。然后取最大值

class Solution:
    def rob(self, root: TreeNode) -> int:
        if not root:
            return 0
        res = root.val
        if root.left:
            res += self.rob(root.left.left) + self.rob(root.left.right)
        if root.right:    
            res += self.rob(root.right.left) + self.rob(root.right.right)
        return return max(res, self.rob(root.left) + self.rob(root.right)) # 偷根节点, 不偷根节点

复杂度过高,会超时。
优化如下:
res[0,0],第一维代表不偷,第二维代表偷

def sub_rob(root):
            if not root:
                return [0, 0]
            res = [0, 0] # 初始化
            left = sub_rob(root.left) # 递归偷左子树
            right = sub_rob(root.right) # 递归偷右子树
            res[0] = max(left[0], left[1]) + max(right[0], right[1]) # res[0]表示不偷根节点,所以要偷左右子树
            res[1] = root.val + left[0] + right[0] # res[1]表示偷根节点,所以不能偷左右子树
            return res
        result = sub_rob(root)
        return max(result)

你可能感兴趣的:(高频算法,leetcode,二叉树,算法,动态规划)