198.打家劫舍
思路
1. 确定 dp 数组含义
dp[i]:下标为 i 以内的房屋,能偷窃到的最大金额。
2. 递推公式
不能偷相邻房屋:
-
偷第 i 间:就不能偷 i−1,金额 = dp[i−2]+nums[i]
-
不偷第 i 间:金额 = dp[i−1]
-
dp[i]=max(dp[i−1], dp[i−2]+nums[i])
3. 初始化
-
dp[0]=nums[0] 只一间房,必偷
-
dp[1]=max(nums[0],nums[1]) 两间房选钱多的
4. 遍历顺序
从左到右,i 从 2 遍历到末尾。
提交
python
class Solution:
def rob(self, nums: List[int]) -> int:
# 边界情况:没有房屋
if len(nums) == 0:
return 0
# 边界情况:只有一间房屋,直接取该金额
if len(nums) == 1:
return nums[0]
n = len(nums)
# dp[i] 表示前i间房屋能偷窃到的最大金额
dp = [0] * n
# 第一间房,只能偷自己
dp[0] = nums[0]
# 前两间房,选金额更大的一间
dp[1] = max(nums[0], nums[1])
# 从第三间房开始遍历推导
for i in range(2, n):
# 两种选择:
# 1.不偷当前房:最大值 = dp[i-1]
# 2.偷当前房:不能偷上一间,最大值 = dp[i-2] + nums[i]
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
# 返回全部房屋的最大偷窃金额
return dp[-1]
213.打家劫舍II
题目链接213. 打家劫舍 II - 力扣(LeetCode)
思路
核心区别:房屋围成一圈,第一间和最后一间不能同时偷。
因为首尾相连,所以分两种情况取最大:
-
不偷最后一间 :只考虑
nums[0] ~ nums[-2] -
不偷第一间 :只考虑
nums[1] ~ nums[-1]
最终答案 = 两种情况的最大值
动规 5 步曲(沿用原版)
-
dp[i]:前 i 间房能偷的最大金额
-
递推公式 :
dp[i] = max(dp[i-1], dp[i-2] + nums[i]) -
初始化 :
dp[0]=nums[0],dp[1]=max(nums[0],nums[1]) -
遍历:从左到右
-
结果:数组最后一位
提交
python
class Solution:
def rob(self, nums: List[int]) -> int:
# 边界:只有一间房,直接返回
if len(nums) == 1:
return nums[0]
# 核心:环形 = 两个线性问题取最大
# 情况1:不偷最后一间 → 范围 [0, len-2]
# 情况2:不偷第一间 → 范围 [1, len-1]
return max(
self.rob_range(nums, 0, len(nums)-2),
self.rob_range(nums, 1, len(nums)-1)
)
# 原版打家劫舍:计算 [start, end] 区间的最大金额
def rob_range(self, nums: List[int], start: int, end: int) -> int:
# 区间只有一间房
if start == end:
return nums[start]
# 初始化dp
dp = [0] * len(nums)
dp[start] = nums[start]
dp[start+1] = max(nums[start], nums[start+1])
# 从第三间开始递推
for i in range(start+2, end+1):
# 不偷当前 = dp[i-1]
# 偷当前 = dp[i-2] + 当前金额
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
return dp[end]
337.打家劫舍III
题目链接 337. 打家劫舍 III - 力扣(LeetCode)
怎么把二叉树搞进来了
思路
题目核心
房屋组成二叉树 ,父子节点不能同时偷,求最大金额。树形 DP,后序遍历,每个节点返回两个状态:
- 选当前节点的最大金额
- 不选当前节点的最大金额
动规五步曲
1.dp 含义
对每个节点,返回长度为 2 的数组:
-
res[0]:不偷当前节点,子树最大金额 -
res[1]:偷当前节点,子树最大金额
2.递推公式
-
偷当前节点:左右孩子都不能偷res[1]=root.val+left[0]+right[0]
-
不偷当前节点:左右孩子可选偷 / 不偷,取最大值res[0]=max(left[0],left[1])+max(right[0],right[1])
3.初始化
空节点return[0,0]
4.遍历顺序
二叉树后序遍历:先左、再右、最后根
5.结果推导
最终答案:max(根节点【0】,根节点【0】)
提交
python
# 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:
# 返回 [不偷当前节点最大值, 偷当前节点最大值]
def dfs(node):
# 空节点,左右都无收益
if not node:
return [0, 0]
left = dfs(node.left) # 左子树状态
right = dfs(node.right) # 右子树状态
# 1. 偷当前节点:左右孩子一定不能偷
rob_cur = node.val + left[0] + right[0]
# 2. 不偷当前节点:左右孩子随便选,取最大
not_rob_cur = max(left[0], left[1]) + max(right[0], right[1])
return [not_rob_cur, rob_cur]
root_res = dfs(root)
# 根节点偷或不偷,取最大值
return max(root_res[0], root_res[1])