看到这题的第一想法是dfs。
1、可以先考虑边界问题:房屋个数有限,不能越界。
if(越界) return 0;
2、不能偷相邻两户
2.1、偷了上一户
2.1.1、这一户不能偷
2.2、没偷上一户
2.2.1、这一户可以偷
2.2.2、这一户可以不偷
2.2.3、两种可能取最优
if(偷了上一户){ return dfs(这一户不能偷);}
else{ return Math.max(dfs(这一户不能偷),dfs(这一户可以偷); }
class Solution {
public int rob(int[] nums) {
return dfs(nums,0,false);
}
/**
now_i 当前元素
flag 上一家有没有偷
*/
public int dfs(int[] nums, int now_i, boolean flag){
if(now_i > nums.length-1) return 0;
if(flag) return dfs(nums,now_i+1,false);
// 上一家没偷 这一家可以偷也可以不偷
else return Math.max(
dfs(nums,now_i+1,false),
dfs(nums,now_i+1,true)+nums[now_i]
);
}
}
很遗憾超时了
通过分析可知道:
可以看到有相当一部分重复计算了,如果做到只计算一次,其他相同的直接复用即可无需再次计算,那便节约了许多时间。
可以用数组来存储:
// 记录 这偷家之后 的最优情况
int m_true[];
// 记录 这不偷家之后 的最优情况
int m_false[];
..............
// -1 表示从未计算过,非负数表示计算得出的值
Arrays.fill(m_true,-1);
Arrays.fill(m_false,-1);
.............
// 计算并存储
if(m_false[i] == -1) m_false[i] = dfs(不偷);
if(m_true[i] == -1) m_true[i] = dfs(偷);
代码如下:
// 记忆优化
class Solution {
// 记录 这偷家之后 的最优情况
int m_true[];
// 记录 这不偷家之后 的最优情况
int m_false[];
public int rob(int[] nums) {
m_true = new int[nums.length];
m_false = new int[nums.length];
Arrays.fill(m_true,-1);
Arrays.fill(m_false,-1);
return dfs(nums,0,false);
}
/**
now_i 当前元素
flag 上一家有没有偷
*/
public int dfs(int[] nums, int now_i, boolean flag){
if(now_i > nums.length-1) return 0;
if(flag) {
if(m_false[now_i] == -1) m_false[now_i] = dfs(nums,now_i+1,false);
return m_false[now_i];
}
else {
// 上一家没偷 这一家可以偷也可以不偷
if(m_false[now_i] == -1) m_false[now_i] = dfs(nums,now_i+1,false);
if(m_true[now_i] == -1) m_true[now_i] = dfs(nums,now_i+1,true)+nums[now_i];
return Math.max(m_false[now_i],m_true[now_i]);
}
}
}
由上面可以启发:用数组效率更高,可以尝试用数组来解。
由于不能偷连续两家,所以可以对比偷不偷当前这家两种结果取最优。
即:Math.max(res[i-2]+nums[i],res[i-1]); res[]表示已偷金额,i表示第几家。
class Solution {
public int rob(int[] nums) {
// 当前解法中,数组后移:0~nums.length -> 1~nums.length
// 所以 Math.max(res[i-2]+nums[i],res[i-1]); -> Math.max(res[i-1]+nums[i],res[i]);
int[] res = new int[nums.length+1];
// 不抢第一家
res[0] = 0;
// 抢第一家
res[1] = nums[0];
for(int i=1; i<nums.length; i++){
// res[i+1] 就是到第i家(包括第i家是否抢 之后已抢的最大总金额)
res[i+1] = Math.max(res[i-1]+nums[i],res[i]);
}
return res[nums.length];
}
}
由于 res[i+1] = Math.max(res[i-1]+nums[i],res[i]); 与 return res[nums.length];
所以旧数据仅仅用来推导出新数据,用完即丢。所以可以用3个变量代替数组
class Solution {
public int rob(int[] nums) {
// i=1 意味着 必须手动判断 nums.length 的情况
if(nums.length == 1) return nums[0];
// 不抢第一家
int first = 0;
// 抢第一家
int second = nums[0];
// 最优金额
int res=0;
for(int i=1; i<nums.length; i++){
res = Math.max(first+nums[i],second);
first = second;
second = res;
}
return res;
}
}
在第一题的基础上,第一家和最后一家是隔壁
三种情况:
1、偷第一 不偷最后
2、不偷第一 偷最后
3、不偷第一 不偷最后
可以用第一题的解法,三种结果取最大。
class Solution {
public int rob(int[] nums) {
// 没得选,只能是它
if(nums.length == 1) return nums[0];
// 只能2选1
if(nums.length == 2) return Math.max(nums[0],nums[1]);
// 只能3选1
if(nums.length == 3) return Math.max(Math.max(nums[0],nums[1]),nums[2]);
/**
三种情况:
1、偷第一 不偷最后
2、不偷第一 偷最后
3、不偷第一 不偷最后
*/
// 1、偷第一 不偷最后
int[] res = new int[nums.length];
res[0] = nums[0];
res[1] = nums[0];
for(int i=2;i<nums.length-1;i++){
res[i] = Math.max(res[i-2]+nums[i],res[i-1]);
}
int max= res[nums.length-2];
// 2、不偷第一 偷最后
res = new int[nums.length];
res[nums.length-1] = nums[nums.length-1];
res[nums.length-2] = nums[nums.length-1];
for(int i=nums.length-3;i>0;i--){
res[i] = Math.max(res[i+2]+nums[i],res[i+1]);
}
max= Math.max(max,res[1]);
// 3、不偷第一 不偷最后
res = new int[nums.length];
res[0] = 0;
res[1] = nums[1];
for(int i=2;i<nums.length-1;i++){
res[i] = Math.max(res[i-2]+nums[i],res[i-1]);
}
max= Math.max(max,res[nums.length-2]);
return max;
}
}