213. 打家劫舍 II(中等)
思路
这道题是 198.打家劫舍
的拓展版,区别在于:本题的房间是环形排列,而198.题中的房间是单排排列。
将房间环形排列,意味着第一间房间和最后一间房间不能同时盗窃,因此可以把这道题简化为两个单排排列的子问题。
- 可以选择第一个房间进行偷窃,也就是考虑
nums[0...n-2]
; - 可以选择最后一个房间进行偷窃,也就是考虑
nums[1...n-1]
;
综合上述两种情况,选择偷窃金额最多的情况作为本题的答案。
状态定义
根据题解,本道题将定义两种状态:
first[i]
:表示可以选择第一个房间的偷窃情况下,到第 i 个房间为止的最多金额;last[i]
:表示可以选择最后一个房间的偷窃情况下,到第 i 个房间为止的最多金额;
状态转移方程
对于每个房间,都有偷窃和不偷窃两种选择,以第 i 个房间为例:
- 如果选择偷窃这个房间 i ,那么前一个房间肯定不能偷窃,所以当前的金额是第 i-2 个房间的金额加上该房间的现金,即
first[i] = first[i-2] + nums[i-1];
- 如果选择不偷窃该房间,所以当前的金额和前一个房间的金额相等,即
first[i] = first[i-1];
我们要得到偷窃到的最高金额,也就是在这两种情况中选择金额最高的,即 first[i] = max(first[i-1], first[i-2] + nums[i-1]);
。
对于 last数组,它的状态转移方程是一致的。
初始化
first [0] = last[0] = 0;
,第 0 个房间,也就意味着还没有实施偷窃,所以金额为 0;first[1] = nums[0] , last[1] = nums[1];
first 和 last 的第一个房间的金额分别设置为 第一个房间的现金。
最终的返回结果
在题解中已经解释了,我们需要返回两种情况下的最大金额, 即 return max(first[n-1], last[n-1]);
。
代码
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n<=3) return *max_element(nums.begin(), nums.end());
vector first(n, 0);
vector last(n, 0);
first[1] = nums[0];
for(int i=2; i<n; ++i){
first[i] = max(first[i-1], first[i-2] + nums[i-1]);
cout<<"i:"<<i<<" "<<first[i]<<endl;
}
last[1] = nums[1];
for(int i=2; i<n; ++i){
last[i] = max(last[i-1], last[i-2] + nums[i]);
}
return max(first[n-1], last[n-1]);
}
};
空间压缩
由于 first[i]
只和前两个状态有关,因此可以进行空间压缩。
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n<=3) return *max_element(nums.begin(), nums.end());
int cur_first , pre1 = 0, pre2 = nums[0];
for(int i=2; i<n; ++i){
cur_first = max(pre2, pre1 + nums[i-1]);
pre1 = pre2;
pre2 = cur_first;
}
int cur_last;
pre1 = 0, pre2 = nums[1];
for(int i=2; i<n; ++i){
cur_last = max(pre2, pre1 + nums[i]);
pre1 = pre2;
pre2 = cur_last;
}
return max(cur_first, cur_last);
}
};
代码简化
显然,两个 for 循环的结构一致,所以可以将 for 循环写成子函数,也可以再定义两个变量,将两个 for循环整合到一起。这里只展示第二种写法:
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n<=3) return *max_element(nums.begin(), nums.end());
int cur_first ,cur_last;
int pre1 = 0, pre2 = nums[0], pre3 = 0,pre4 = nums[1];
for(int i=2; i<n; ++i){
cur_first = max(pre2, pre1 + nums[i-1]);
cur_last = max(pre4, pre3 + nums[i]);
pre1 = pre2;
pre2 = cur_first;
pre3 = pre4;
pre4 = cur_last;
}
return max(cur_first, cur_last);
}
};