代码随想录算法训练营第四十七天| 198.打家劫舍 213.打家劫舍II 337.打家劫舍III

文档讲解:代码随想录

视频讲解:代码随想录B站账号

状态:看了视频题解和文章解析后做出来了

198.打家劫舍

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(nums[i] + dp[i-2], dp[i-1])

        return dp[-1]
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

1. 确定dp数组以及下标的含义

dp[i] : 打家劫舍到下标为i的房子时,能够抢劫到的最大金额。

2. 确定递推公式

在达到一个房子的时候,有两种情况要考虑:

1. 是否抢劫这个房子,如果抢劫而不触发警报,就要在i-2的基础上加上当前房子的价值,即dp[i-2] + nums[i]

2. 不抢这个房子,那就要考虑dp[i-1]的房子作为直到当前房子的最大抢劫价值。这里不用考虑 i - 3或 i - 4是因为 i - 1因为这个递推公式一定比之前要大。

3. dp数组的初始化

从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]

从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);

4. 遍历顺序

从前向后遍历

5. dp数组举例

代码随想录算法训练营第四十七天| 198.打家劫舍 213.打家劫舍II 337.打家劫舍III_第1张图片

213.打家劫舍II

class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) < 3:
            return max(nums)

        # 情况二:不抢劫第一个房屋
        result1 = self.robRange(nums[:-1])

        # 情况三:不抢劫最后一个房屋
        result2 = self.robRange(nums[1:])

        return max(result1, result2)

    def robRange(self, nums):
        dp = [[0, 0] for _ in range(len(nums))]
        dp[0][1] = nums[0]

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

        return max(dp[-1])
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

与上一题不同,这一题的房屋连成了一个圈,所以要考虑两种情况。

1. 考虑第一个房屋,不考虑最后一个房屋

2. 考虑最后一个房屋,不考虑第一个房屋

分别在两种情况下得出最大的抢劫价值,返回它们中的更大者。

这里用到了二维dp数组,其中dp[i][0]的i表示第i个房屋,0就代表没有抢劫,1代表抢劫了。

如果不抢劫当前的房屋,那么取上一个房屋抢劫or不抢劫中的最大值。

如果抢劫当前的房屋,那么取上一个房屋不抢劫的值(因为警示灯)加上当前房屋的价值。

337.打家劫舍III

# 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:
        # dp数组(dp table)以及下标的含义:
        # 1. 下标为 0 记录 **不偷该节点** 所得到的的最大金钱
        # 2. 下标为 1 记录 **偷该节点** 所得到的的最大金钱
        dp = self.traversal(root)
        return max(dp)

    # 要用后序遍历, 因为要通过递归函数的返回值来做下一步计算
    def traversal(self, node):
        
        # 递归终止条件,就是遇到了空节点,那肯定是不偷的
        if not node:
            return (0, 0)

        left = self.traversal(node.left)
        right = self.traversal(node.right)

        # 不偷当前节点, 偷子节点
        val_0 = max(left[0], left[1]) + max(right[0], right[1])

        # 偷当前节点, 不偷子节点
        val_1 = node.val + left[0] + right[0]

        return (val_0, val_1)
  • 时间复杂度:O(n),每个节点只遍历了一次
  • 空间复杂度:O(log n),算上递推系统栈的空间

二叉树遍历首先需要确定的是遍历顺序,这里用到后序遍历,是因为母节点需要两个子节点的返回值来确定它自己的值。

同样两种情况:

1. 偷当前节点,那就不能偷子节点。当前的.val加上左右子节点的[0],即没偷左右子节点的最大值。

2. 不偷当前节点,那就偷子节点。返回两个子节点能偷的最大值的最大值。

一路遍历上来,最后返回dp的最大值,也就是这个tuple两个值中的最大值。

你可能感兴趣的:(算法,leetcode,职场和发展)