1. 题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
1.1 示例
- 示例 1 1 1:
- 输入:
[1, 2, 3, 1]
- 输出: 4 4 4
- 解释: 偷窃 1 1 1 号房屋 (金额 = 1 1 1) ,然后偷窃 3 3 3 号房屋 (金额 = 3 3 3)。偷窃到的最高金额 = 1 + 3 = 4 = 1 + 3 = 4 =1+3=4 。
- 示例 2 2 2:
- 输入:
[2, 7, 9, 3, 1]
- 输出: 12 12 12
- 解释: 偷窃 1 1 1 号房屋 (金额 = 2 2 2),偷窃 3 3 3 号房屋 (金额 = 9 = 9 =9),接着偷窃 5 5 5 号房屋 (金额 = 1 = 1 =1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 = 2 + 9 + 1 = 12 =2+9+1=12 。
1.2 说明
- 来源: 力扣(LeetCode)
- 链接:https://leetcode-cn.com/problems/house-robber
1.3 提示
1 <= nums.length <= 100
0 <= nums[i] <= 400
1.4 进阶
你可以进一步输出得到的最高金额是如何得到的么?
2. 解法一(自底向上动态规划)
2.1 分析
首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。
如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?实际上,对于第 k k k ( k > 2 k>2 k>2) 间房屋,有两个选项:
- 偷窃第 k k k 间房屋,那么就不能偷窃第 k − 1 k-1 k−1 间房屋(因为按照题意,相邻两间房屋不可同时偷窃),最高总金额为偷窃前 k − 2 k−2 k−2 间房屋的最高总金额与第 k k k 间房屋的金额之和;
- 不偷窃第 k k k 间房屋,最高总金额为偷窃前 k − 1 k−1 k−1 间房屋的最高总金额。
在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前
k
k
k 间房屋能偷窃到的最高总金额。用 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])
2.2 解答
from typing import List
class Solution:
def iterative_rob(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
if len(nums) == 2:
return max(nums[0], nums[1])
dp = [0 for _ in range(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 - 2] + nums[i], dp[i - 1])
return dp[-1]
def main():
nums = [1, 2, 3, 1, 1, 2, 3, 6]
sln = Solution()
print(sln.iterative_rob(nums)) # 12
if __name__ == '__main__':
main()
- 执行用时: 28 ms , 在所有 Python3 提交中击败了 87.66% 的用户;
- 内存消耗: 14.9 MB , 在所有 Python3 提交中击败了 63.56% 的用户。
2.3 复杂度
实际上,上述方法使用了数组存储结果。考虑到每次迭代过程中只用到了截至前两个房屋的最大偷窃金额,因此可以不事先分配一个数组,以进一步降低空间复杂度:
from typing import List
class Solution:
def efficient_iterative_rob(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
if len(nums) == 2:
return max(nums[0], nums[1])
prev, cur, nxt = nums[0], max(nums[0], nums[1]), 0
for i in range(2, len(nums)):
nxt = max(prev + nums[i], cur)
prev, cur = cur, nxt
return cur
def main():
nums = [1, 2, 3, 1, 1, 2, 3, 6]
sln = Solution()
print(sln.efficient_iterative_rob(nums)) # 12
if __name__ == '__main__':
main()
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组长度。只需要对数组遍历一次。
- 空间复杂度: O ( 1 ) O(1) O(1)。仅需要使用常数个变量存储中间结果。