打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 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
常规思路:假设当小偷打劫第i间房屋时,因为不能打劫连续的房屋,所以小偷打劫的上一间屋子可能是前i-2间屋子中的任意一间。所以只要找出前i-2间屋子中收益最高的那间屋子就可以了。
设数组dp[i],表示打劫第i间屋子的情况下可以获得的最大收益,则状态转移方程为:
dp[i] = Max( dp[0] , dp[1] , ... ,dp[i-2] ) + nums[i],时间复杂度为O(N^2)
当小偷走到最后时,他要么打劫倒数第二家,要么打劫最后一家,最后取这两种情况的最大值
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
int[] dp=new int[nums.length];
dp[0]=nums[0];
dp[1]=nums[1];
for(int i=2;i<nums.length;i++){
int max=Integer.MIN_VALUE;
for(int j=0;j<i-1;j++){
if(dp[j]>max){
max=dp[j];
}
dp[i]=max+nums[i];
}
}
return Math.max(dp[nums.length-1], dp[nums.length-2]);
解法二
由于不可以在相邻的房屋闯入,所以在当前位置 n 房屋可盗窃的最大值,要么就是 n-1 房屋可盗窃的最大值,要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值
举例来说:1 号房间可盗窃最大值为 33 即为 dp[1]=3,2 号房间可盗窃最大值为 44 即为 dp[2]=4,3 号房间自身的值为 22 即为 num=2,那么 dp[3] = MAX( dp[2], dp[1] + num ) = MAX(4, 3+2) = 5,3 号房间可盗窃最大值为 55
时间复杂度:O(n),n 为数组长度
状态转移方程:dp[n] = MAX( dp[i-1], dp[i-2] + num[i]
dp[i]代表抢到第i间房间时的最大收益,第i间房子抢不抢不一定
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
int[] dp=new int[nums.length];
dp[0]=nums[0];
dp[1]=Math.max(nums[0], nums[1]);
for(int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
打家劫舍2
房屋变成了环形,也就是第一间房屋和最后一间房屋相连,那么我们就不能同时抢劫第一间和最后一间。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路:
这题和上面的题目十分类似,只多了一个不能同时抢劫首尾两间房屋的条件,那么我们可以将问题转化为:
- 去除掉最后一间房屋的情况下抢劫的最高收益
- 去除掉第一间房屋情况下抢劫的最高收益
最后选取两种情况下的最高收益。
状态转移方程和上面相同:dp[n] = MAX( dp[i-1], dp[i-2] + num[i]
public int rob(int[] nums) {
if(nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
if(nums.length==2){
return Math.max(nums[0],nums[1]);
}
//去除了一个元素,dp数组长度也减少一个
int[] dp=new int[nums.length-1];
//去掉最后一间的情况
dp[0]=nums[0];
dp[1]=Math.max(nums[0], nums[1]);
int res1,res2;
for(int i=2;i<nums.length-1;i++){
dp[i]=Math.max(dp[i-1], dp[i-2]+nums[i]);
}
res1=dp[nums.length-2];
//去掉第一间一间的情况
dp[0]=nums[1];
dp[1]=Math.max(nums[1], nums[2]);
for(int i=2;i<nums.length-1;i++){
dp[i]=Math.max(dp[i-1], dp[i-2]+nums[i+1]);
}
res2=dp[nums.length-2];
//去两种情况最大值
return Math.max(res1, res2);
}