你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
示例 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 。
提示:
1 <= nums.length <= 1000 <= nums[i] <= 400
解题思路
-
状态定义 :用
prev_prev记录 "偷到前前间房时的最大金额",prev记录 "偷到前一间房时的最大金额"(替代传统的dp数组,优化空间复杂度至 O (1))。 -
状态转移 :对于第
i间房,有两种选择:- 不偷当前房 :最大金额等于 "偷到前一间房的金额"(即
prev); - 偷当前房 :最大金额等于 "偷到前前间房的金额 + 当前房的金额"(即
prev_prev + nums[i]);取两者的较大值作为当前房的最大金额,并更新前序状态。
- 不偷当前房 :最大金额等于 "偷到前一间房的金额"(即
-
边界处理:
- 空数组直接返回 0;
- 只有 1 间房时,直接返回该房的金额。
示例验证
-
示例 1:输入
[1,2,3,1]遍历过程:- 初始:
prev_prev=1,prev=2 - 第 3 间房(值 3):
current = max(2, 1+3=4)→prev=4 - 第 4 间房(值 1):
current = max(4, 2+1=3)→ 最终返回4。
- 初始:
-
示例 2:输入
[2,7,9,3,1]遍历过程:- 初始:
prev_prev=2,prev=7 - 第 3 间房(值 9):
current = max(7, 2+9=11)→prev=11 - 第 4 间房(值 3):
current = max(11, 7+3=10)→prev=11 - 第 5 间房(值 1):
current = max(11, 11+1=12)→ 最终返回12。
- 初始:
Python代码:
python
from typing import List
class Solution:
def rob(self, nums: List[int]) -> int:
"""
计算不触动警报时能偷窃的最高金额(空间优化版:O(1)空间复杂度)
:param nums: 非负整数数组,每个元素代表对应房屋的现金金额
:return: 能偷窃的最高金额
"""
# 边界条件1:空数组(题目提示长度≥1,此判断为鲁棒性补充)
if not nums:
return 0
# 边界条件2:只有1间房,直接偷这间
if len(nums) == 1:
return nums[0]
# 状态初始化:
# prev_prev: 偷到「前前间房」时的最大金额(对应dp[i-2])
# prev: 偷到「前一间房」时的最大金额(对应dp[i-1])
prev_prev = nums[0]
prev = max(nums[0], nums[1]) # 前两间房选金额大的偷
# 从第3间房开始遍历(索引2),逐间推导最大金额
for i in range(2, len(nums)):
# 状态转移核心:
# 偷当前房:不能偷前一间,金额=prev_prev + 当前房金额
# 不偷当前房:金额=prev(前一间的最大金额)
current = max(prev, prev_prev + nums[i])
# 更新前序状态:为下一间房的计算做准备
prev_prev, prev = prev, current
# 最终prev存储的是偷到最后一间房时的最大金额
return prev
def rob_dp_array(self, nums: List[int]) -> int:
"""
传统DP数组版(O(n)空间复杂度,更易理解)
:param nums: 房屋金额数组
:return: 最高偷窃金额
"""
if not nums:
return 0
n = len(nums)
if n == 1:
return nums[0]
# dp[i]:偷到第i间房(索引i)时的最大金额
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 - 1], dp[i - 2] + nums[i])
return dp[-1]
# ---------------------- 测试用例 ----------------------
if __name__ == "__main__":
solution = Solution()
# 测试示例1
nums1 = [1, 2, 3, 1]
res1 = solution.rob(nums1)
print(f"示例1输入:{nums1}")
print(f"示例1输出(空间优化版):{res1}(预期:4)")
print(f"示例1输出(DP数组版):{solution.rob_dp_array(nums1)}(预期:4)")
print("-" * 30)
# 测试示例2
nums2 = [2, 7, 9, 3, 1]
res2 = solution.rob(nums2)
print(f"示例2输入:{nums2}")
print(f"示例2输出(空间优化版):{res2}(预期:12)")
print(f"示例2输出(DP数组版):{solution.rob_dp_array(nums2)}(预期:12)")
print("-" * 30)
# 额外测试:单元素数组
nums3 = [5]
res3 = solution.rob(nums3)
print(f"额外测试(单元素):{nums3} → {res3}(预期:5)")
# 额外测试:两元素数组
nums4 = [3, 7]
res4 = solution.rob(nums4)
print(f"额外测试(两元素):{nums4} → {res4}(预期:7)")
LeetCode提交代码:
python
from typing import List
class Solution:
def rob(self, nums: List[int]) -> int:
# 处理空数组或单元素数组的特殊情况
if not nums:
return 0
if len(nums) == 1:
return nums[0]
# 初始化前两个状态:prev_prev对应"前前间房的最大金额",prev对应"前一间房的最大金额"
prev_prev = nums[0]
prev = max(nums[0], nums[1])
# 从第3间房开始遍历(索引为2)
for i in range(2, len(nums)):
# 当前房的最大金额 = max(不偷当前房(取前一间的金额), 偷当前房(取前前间+当前房的金额))
current = max(prev, prev_prev + nums[i])
# 更新前序状态
prev_prev, prev = prev, current
return prev
程序运行结果展示:
bash
示例1输入:[1, 2, 3, 1]
示例1输出(空间优化版):4(预期:4)
示例1输出(DP数组版):4(预期:4)
------------------------------
示例2输入:[2, 7, 9, 3, 1]
示例2输出(空间优化版):12(预期:12)
示例2输出(DP数组版):12(预期:12)
------------------------------
额外测试(单元素):[5] → 5(预期:5)
额外测试(两元素):[3, 7] → 7(预期:7)
总结
本文解决房屋盗窃问题,要求在不相邻房屋中获取最高金额。采用动态规划方法,定义状态prev_prev和prev分别表示前前间和前一间的最大金额。状态转移方程为current = max(prev, prev_prev + nums[i]),优化空间复杂度至O(1)。示例验证显示算法正确性,如输入[1,2,3,1]输出4,输入[2,7,9,3,1]输出12。Python代码实现包含空间优化版和传统DP数组版,边界处理覆盖空数组和单元素情况。运行结果与预期一致,证明算法有效性。