代码随想录算法训练营第三十九天 |动态规划part07

49 阅读3分钟

代码随想录算法训练营第三十九天 |动态规划part07

198 打家劫舍I

image-20250117223111145.png

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

    • dp[i] 考虑下标i(包含)偷的最大的金币dp[i]
  • 递推公式

    • 偷i dp[i-2] + num[i]
    • 不偷i dp[i-1]
    • dp[i] = max(dp[i-2] + num[i],dp[i-1])
  • dp数组如何初始化

    • dp[0] = nums[0]
    • dp[1] = max(nums[0],nums[1])
  • 遍历顺序

    • for i in range(2,n):
  • 打印dp数组

    • return dp[-1]

完整代码:

nums = [1,2,3,1]
# dp[i] 偷窃i个房屋的最大金额
n = len(nums)
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])
print(dp)

213 打家劫舍II

image-20250117223119363.png

打家劫舍I是线性的数组,打家劫舍II变成了环形,首尾相连。

分成三种情况:

  • 首尾皆不考虑,只考虑中间部分
  • 考虑首,不考虑尾
  • 考虑尾,不考虑首

二三情况其实是包含第一种情况的。

# nums = [2,3,2]
def f(nums,dp):
    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]
# f(nums[:len(nums)-1],[])
# f(nums[1:],[])
if len(nums) == 1:
    return nums[0]
elif len(nums) == 2:
    return max(nums[0],nums[1])
else:
    return max(f(nums[:len(nums)-1],[]),f(nums[1:],[]))

337 打家劫舍III

image-20250117223129613.png

树形DP:在二叉树中进行状态转移。

每个节点是有两种状态,偷还是不偷。

所以dp[i] = 0 or 1 等于0的话就是不偷,等于1的话就是偷。

dp数组(dp table)以及下标的含义:
1. 下标为 0 记录 **不偷该节点** 所得到的的最大金钱
2. 下标为 1 记录 **偷该节点** 所得到的的最大金钱

所以需要定义一个函数,需要是后序遍历,用来遍历所有节点的情况。

if not node:
    return (0,0)

然后进行左右遍历,

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

此时有两种情况,偷当前节点

val_0 = node.val + left[0] + right[0] 

不偷当前节点

val_1 = max(left[0],left[1]) + max(right[0],right[1])

确定函数返回值

return (val_1,val_0)

完整代码如下

def rob(self, root: Optional[TreeNode]) -> int:
    dp = self.travelsal(root)
    return max(dp)
​
def travelsal(self,node):
    if not node:
        return (0,0)
​
    left = self.travelsal(node.left)
    right = self.travelsal(node.right)
    # 偷当前节点
    val_1 = node.val + left[0] + right[0]
    # 不偷当前节点
    val_0 = max(left[0],left[1]) + max(right[0],right[1])
    return (val_0,val_1)

本人理解:(不知道正确与否)

进行后序遍历,然后每个节点都是需要向上一层进行状态转移。 如果偷的话,val_1 = node.val + left[0] + right[0] left[0] right[0] 均表示不偷子节点 这是因为我们每个节点都是带着之前节点的状态,所以需要加上孩子的最大值。 val_0 = max(left[0],left[1]) + max(right[0],right[1]) 因为不偷当前节点,所以左右孩子节点可偷可不偷,进行最大值的比较,决定偷不偷。max(left[0],left[1]) + max(right[0],right[1]) 如果不偷的话,那就是左孩子右孩子的最大值加起来,就是这个节点的值,然后传递给上一层。

另外,函数的返回值是一个集合,传递到根节点之后,根节点会有选与不选两种状态,所以直接进行两种状态的值进行比较,选择大的。

dp = (val_0,val_1) dp[0]表示不偷 dp[1]表示偷 之后的最大价值。