假设你是一个小偷,计划盗窃沿街的房屋,每个房屋都藏有一定数量的现金,影响你盗窃的唯一制约是每两间相邻的房屋装有相互连通的报警装置,如果你盗窃了两间相邻的房屋就会触发报警装置。
给定一个非负整数数组表示每个房屋藏有的现金金额。计算在不触动报警装置的情况下,能盗窃到的最大金额。
例子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:暴力法。最简单的方法是穷举所有的盗窃方案,然后找到对应的最大盗取金额。
问题的解空间树如下所示(以例1为例说明):
只要使用深度优先搜索这颗树即可,伪代码如下所示:
#dfs(current, len, curcash, maxcash, nums):
*if(current + 2 < len):
#maxcash = max(curcash,maxcash)
#return
*for(next = current + 2; next < len; next++):
#curcash += nums[next]
#dfs(next, len, curcash, maxcash, nums)
#curcash -= nums[next]
注意,这种方法的时间复杂度并不是指数级的,因为解空间树的分支数量为 ( n − 2 ) + ( n − 3 ) + . . . + 1 = ( n − 1 ) ( n − 2 ) 2 (n-2)+(n-3)+...+1=\frac{(n-1)(n-2)}{2} (n−2)+(n−3)+...+1=2(n−1)(n−2),因此时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度为递归的深度,为 O ( n ) O(n) O(n)。
方法2:动态规划1. 从上面的解空间树可以看出,存在最优子结构,例如,盗窃的最后一个房屋为4号房屋所能获得最大金额等于盗窃的最后一个房屋为2号房屋所能获得最大金额+4号房屋的金额,与盗窃的最后一个房屋为1号房屋所能获得的最大金额+4号房屋的金额,这两者之间的最大值。
根据这一个发现,我们采用与LIS相同的动态规划思路来解决这个问题:
(1)定义状态
d p [ i ] dp[i] dp[i]:盗窃的最后一个房屋为第 i i i号房子所能获得的最大金额;
(2)状态转移
d p [ i ] = m a x { d p [ i ] , d p [ j ] + n u m s [ i ] } , j ∈ [ 0 , i − 2 ] dp[i] =max\{dp[i],dp[j] + nums[i] \},j\in[0,i-2] dp[i]=max{ dp[i],dp[j]+nums[i]},j∈[0,i−2]
盗窃的最后一个房屋为第 i i i号房所能获得的最大金额等于以前 i − 2 i-2 i−2个房子中的第 j j j号房屋能获得的金额的最大值加上第 i i i号房屋所藏的金额。
(3)确定初始
d p [ i ] = n u m s [ i ] , i < 2 dp[i] = nums[i], i<2 dp[i]=nums[i],i<2
(4)确定终止
m a x { d p [ i ] } , i ∈ [ 0 , l e n ) max\{dp[i]\}, i\in[0,len) max{ dp[i]},i∈[0,len)
时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)。
方法3:动态规划2。从另一个角度来看这个问题,假设你是小偷,对于第 i i i家,你有两种选择,打劫或不打劫(0、1问题)。
所以打劫第 i i i家获得的最大金额应该是这两种选择中获得的最优解。
(1)定义状态
d p [ i ] dp[i] dp[i]:打劫前 i i i家所能获得的金额;
(2)状态转移
d p [ i ] = m a x { d p [ i − 1 ] , d p [ i − 2 ] + n u m s [ i ] } dp[i] = max\{dp[i-1],dp[i-2]+nums[i]\} dp[i]=max{ dp[i−1],dp[i−2]+nums[i]}
(3)确定初始
d p [ 0 ] = n u m s [ 0 ] dp[0] = nums[0] dp[0]=nums[0]
d p [ 1 ] = m a x { n u m s [ 0 ] , n u m s [ 1 ] } dp[1] = max\{nums[0],nums[1]\} dp[1]=max{ nums[0],nums[1]}
(4)确定终止
d p [ l e n − 1 ] dp[len-1] dp[len−1]
优化:由于求每一步只用到了前两个最大值,因此只要两个变量就够了。
// class Solution {
// public:
// int rob(vector& nums) {
// int len = nums.size();
// vector maxcash(len,0);
// for(int i = 0; i < len; ++i){
// if(i < 2){
// maxcash[i] = nums[i];
// continue;
// }
// for(int j = i - 2; j >=0; --j){
// maxcash[i] = max(maxcash[i], maxcash[j] + nums[i]);
// }
// }
// int ans = 0;
// for(int k = 0; k < len; ++k){
// ans = max(ans,maxcash[k]);
// }
// return ans;
// }
// };
class Solution{
public:
int rob(vector<int>& nums){
int len = nums.size();
if(len == 0){
return 0;
}
if(len == 1){
return nums[0];
}
if(len == 2){
return max(nums[0],nums[1]);
}
int lasttwo = nums[0];
int lastone = max(nums[0], nums[1]);
for(int i = 2; i < len; ++i){
int temp = lastone;
lastone = max(lastone, lasttwo + nums[i]);
lasttwo = temp;
}
return lastone;
}
};
问题:打家劫舍II
(1)如果房屋围成一个圆圈,即表示每个房屋所藏金钱的非负整数数组为环形数组,计算小偷所能盗窃的最大金额。
示例1:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
分析:围成一个圈,这意味着偷了第一家就不能偷最后一家,即只能选择在第1~至len -1家盗窃或第0至len - 2家盗窃。最后能获得的最大金额为这两者中的最大值。
class Solution {
public:
int rob(vector<int>& nums) {
int len = nums.size();
if(len == 0){
return 0;
}
if(len == 1){
return nums[0];
}
//盗窃1~len-1号房子所能获得的最大金额;
int gain1 = robhelper(nums,1,len - 1);
// cout<<"gain1="<
//盗窃0~len-2号房子所能获得的最大金额;
int gain2 = robhelper(nums, 0, len - 2);
// cout<<"gain2="<
return max(gain1, gain2);
}
int robhelper(vector<int>& nums, int start, int end){
int len = end - start + 1;
if(len == 1){
return nums[start];
}
if(len == 2){
return max(nums[start], nums[start + 1]);
}
int lasttwo = nums[start];
int lastone = max(nums[start], nums[start + 1]);
for(int i = start + 2; i <= end; ++i){
int temp = lastone;
lastone = max(lastone, lasttwo + nums[i]);
lasttwo = temp;
}
return lastone;
}
};
问题:打家劫舍III
(2)这个扩展问题是将房屋组织成一个二叉树,限制同样是如果在同一天打劫了相邻的两间房屋,就会触发报警装置,计算在不触发报警装置的情况下,能盗窃到的最大金额。
示例1:
输入: [3,2,3,null,3,null,1]
3 / \ 2 3 \ \ 3 1
输出: 7 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3 / \ 4 5 / \ \ 1 3 1
输出: 9 解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
分析:对于每个节点 r o o t root root(每一间房屋),有两种选择:
class Solution {
public:
int rob(TreeNode* root) {
vector<int> ans = robhelper(root);
return max(ans[0],ans[1]);
}
vector<int> robhelper(TreeNode* root){
vector<int> status(2,0);
if(root == NULL){
return status;
}
vector<int> lstatus = robhelper(root->left);
vector<int> rstatus = robhelper(root->right);
status[1] = lstatus[0] + rstatus[0] + root->val;
status[0] = max(lstatus[0],lstatus[1]) + max(rstatus[0],rstatus[1]);
return status;
}
};