打家劫舍Ⅱ
题目要求
解题思路
- [打家劫舍Ⅱ]是说两个相邻的房间不能同时偷,并且首尾两个房间是相邻的(不能同时偷首尾房间)
- 明显是基于[打家劫舍Ⅰ]做的升级。[打家劫舍Ⅰ]也是说两个相邻的房间不能同时偷,但是首尾房间不是相邻的(可以同时偷首尾房间)
所以,我们先从[打家劫舍Ⅰ]开始说起。
打家劫舍Ⅰ
题目:两个相邻的房间不能同时偷,首尾房间不相邻,求小偷能获取的最大金额。
对于[求数组中按照某种方法进行选择,求最值,而不用知道具体选择方案]的问题,可以考虑动态规划。动态规划最基本的是[状态的定义],然后比较困难的是[状态转移方程]。
[状态定义]即dp[i]
,一般可以根据题意,题目要求什么,我们就定义什么。比如本题,我们定于dp[i]
为数组的前i个元素中按照[两个相邻的房间不能同时偷]的方法,能够获得到的最大值。(经验:定义dp[i]
为数组的前i个元素的结果)
考虑[状态转移方程]是,一定要想办法让dp[i]
能够基于dp[0~i-1]
生成。本题要求不能同时偷相邻的房间。所以,dp[i]有两种选择:num[i]选或者不选。
- 如果
num[i]
选,那么由于不能选择相邻的房间,所以不可以选择num[i-1]
,所以选择num[i]
的情况下,数组的前i个元素构成的最大值dp[i]=dp[i-2]+num[i]
; - 如果
num[i]
不选,那么就可以选择num[i-1]
,所以数组的前i个元素构成的最大值 等于 数组前i-1个元素构成的最大值,即dp[i]=dp[i-1]
- 所以,最终的
dp[i]
是上面两种情况的最大值。
[初始条件]比较简单:
dp[0] = num[0]
dp[1] = max(dp[0],num[1]) = max(num[0], num[1])
[返回结果],可以根据我们的dp[i]
知道最终要求的是在整个数组上能够取得的最大值。所以返回dp[N-1]
打家劫舍Ⅱ
在多了数组的开头和结尾是相邻的情况下,也就是说,数组的开头和结尾元素不能同时选。由于状态转移方程中,是没有标记我们到底选了哪些元素的。所以如果想通过状态转移方程,来实现首尾元素不能同时选,是很难的。
这里就用上了技巧,分为两种情况去考虑:分别在nums[0:N-1]
上计算能获得到的最大值,这两种个情况取最大。这肯定能保证在物理上隔离了首尾两个元素,肯定不会同时选到。
代码
class Solution:
def rob(self, nums: List[int]) -> int:
N = len(nums)
if not nums:
return 0
if N == 1:
return nums[0]
return max(self.rob1(nums[0:N - 1]), self.rob1(nums[1:N]))
def rob1(self,nums:List[int]):
N = len(nums)
if not nums:
return 0
if N == 1:
return nums[0]
# max amount [0, 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 - 2] + nums[i], dp[i - 1])
return dp[-1]
复杂度分析
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)