打家劫舍
描述
简单
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 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 。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
解题
不能偷相邻的房屋
公式推导
-
如果只有一间房屋,就偷那一间
-
如果有两间,偷金额大的那一间
-
如果有三间
- 方案一,偷第三间。偷第一间和第三间
- 方案二,不偷第三间。偷第二间
- 选择方案一和方案二中可以获得金额大的方案
-
如果有k间
- 方案一,偷第k间。获得前k-2间的最高金额和第k间金额
- 方案二,不偷第三间。获得前k-1间的最高金额
- 选择方案一和方案二中可以获得金额大的方案
然后动态规划式子就出来了
用dp[i] 表示前i间房子能偷到的最高金额
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
2
]
+
n
u
m
s
[
i
]
,
d
p
[
i
−
1
]
)
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
size = len(nums)
if size == 1:
return nums[0]
dp = [0 for _ in range(size)]
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, size):
dp[i] = max(nums[i] + dp[i - 2], dp[i - 1])
return dp[size - 1]
dp[i] 的空间复杂度为O(n),dp[i] 时就使用了dp[i-1] 和dp[i-2] 两个数据
可以优化,使得空间复杂度变为O(1)
class Solution:
def rob(self, nums: List[int]) -> int:
cur, pre = 0, 0
for n in nums:
# 等号右边的pre表示dp[i-2], cur表示dp[i-1]
# 等号左边cur表示dp[i],pre表示dp[i-1]
cur, pre = max(pre + n, cur), cur
return cur
打家劫舍II
描述
中等
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
解题
和上一题的不同之处在于,第一间房屋和最后一间房屋不能同时偷,只能选择其中一个偷或都不偷
那么可以简化下问题
-
不偷第一间房屋,在nums[1:]的情况下,可以获得的最大金额m1
-
不偷最后一间房屋,在nums[:-1]的情况下,可以获得的最大金额m2
-
可以获得的最大金额为
m a x ( m 1 , m 2 ) max(m1,m2) max(m1,m2)
class Solution:
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return nums[0]
return max(self.my_rob(nums[1:]), self.my_rob(nums[:-1]))
def my_rob(self, nums):
cur, pre = 0, 0
for n in nums:
cur, pre = max(pre + n, cur), cur
return cur
打家劫舍III
描述
中等
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
解题
劳模小偷
这次变成了树的结构
- 偷当前节点时,不能偷两个子节点
- 不偷当前节点时,选择两个子节点(不一定要偷,钱最多就行)
为每一个节点(root)设置一个大小为2的数组res
-
res[0] 表示不偷当前节点能获得的最大金额
- 左孩子能获得的钱 + 右孩子能获得的钱
- 左孩子能获得的钱
- max(rob(root.left)[0], rob(root.left)[1])
- 右孩子能获得的钱
- max(rob(root.right)[0], rob(root.right)[1])
-
res[1] 表示偷当前节点能获得的最大金额
- 左孩子不偷情况下能获得的钱 + 右孩子不偷情况下能获得的钱 + 当前节点的金钱
- 左孩子不偷情况下能获得的钱
- rob(root.left)[0]
- 右孩子不偷情况下能获得的钱
- rob(root.right)[0]
那么就可以得到代码
class Solution:
def rob(self, root: TreeNode) -> int:
res = self.my_rob(root)
return max(res[0], res[1])
def my_rob(self, root):
res = [0, 0]
if not root:
return res
left = self.my_rob(root.left)
right = self.my_rob(root.right)
res[0] = max(left[0], left[1]) + max(right[0], right[1])
res[1] = left[0] + right[0] + root.val
return res