对于打家劫舍的问题,我们可以利用动态规划,利用一个数组dp来记录当前节点所能记录的最大金额,而对于最大金额,即是,偷这一家和不偷这一家的中选择一个最大值,如果偷这一家,则说明不能偷前一家,所以,就从前两家的总金额加上这一家的金额与前一家的金额,最后返回dp[n]即可。
回溯四部曲
1.dp的含义为截止到第ii家所能获取的最大金额(可偷第ii家也可以不偷第ii家)。
2.dp的推导公式为:dp = max(dp[ii-1],dp[ii-2]+nums[ii])
3.dp的初始化,dp[0]=nums[0],dp[1] = max(nums[0],nums[1]),
4.dp的遍历顺序为从前往后
class Solution {
public:
int rob(vector<int>& nums) {
vector<int>dp(nums.size(),0);
if(nums.size()==1) return nums[0];
if(nums.size()==2) return max(nums[0],nums[1]);
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
for(int ii =2;ii<nums.size();ii++)
{
dp[ii] = max(dp[ii-1],dp[ii-2]+nums[ii]);
}
return dp[dp.size()-1];
}
};
我们记录每一次都偷第ii家,那么最后的结果一定是偷了最后一家或者没有偷最后一家,也就是偷了第ii家或者偷了第ii-1家,因此是dp[ii]与dp[ii-1]中的较大者。而对于dp[ii]为偷了第ii家的最大值,因此其值得计算一定是不偷第ii-1家,因此就是dp[ii] = max( dp[ii-2] , dp[ii-3])+nums[ii];
因此,我们的回溯四部曲
1.dp的含义为偷了第ii家所能获得的最大金额。
2.dp的递推公式为:dp[ii] = max( dp[ii-2] , dp[ii-3])+nums[ii];
3.dp的初始化,dp[0]=nums[0],dp[1] = nums[1],dp[2] = nums[0] + nums[2];
4.遍历顺序为从前往后进行遍历。
class Solution {
public:
int rob(vector<int>& nums) {
vector<int>dp(nums.size(),0);
if(nums.size()==1) return nums[0];
if(nums.size()==2) return max(nums[0],nums[1]);
dp[0] = nums[0];
dp[1] = nums[1];
dp[2] = nums[0]+nums[2];
for(int ii =3;ii<nums.size();ii++)
{
dp[ii] = max(dp[ii-2],dp[ii-3])+nums[ii];
}
return max(dp[nums.size()-1],dp[nums.size()-2]);
}
};
相对于打家劫舍,这道题多了一项要求,就是不能连续偷第一家和最后一家,因此,为了防止头元素与尾元素同时被包含,我们可以将元素的首元素和尾元素分别去掉,然后就变成了普通的打家劫舍问题,我们分别进行打家劫舍的求解,最后求两次解的最大值,也就是最终的答案。
对此,我们可以写一个函数,进行打家劫舍的求解,此函数可以用上述的两种方法的任意一种。
int mon(const vector<int>& nums,int start,int end)
{
if(end == start+1) return nums[start];
else if(end == start+2) return max(nums[end-1],nums[start]);
vector<int> dp(end-start,0);
dp[0] = nums[start];
dp[1] = max(nums[start+1],nums[start]);
for(int ii =2;ii<end-start;ii++)
dp[ii] = max(dp[ii-1],dp[ii-2]+nums[start+ii]);
return dp[dp.size()-1];
}
对此,我们也可以写出代码,分别的首元素与尾元素进行去除,来进行求解。
class Solution {
public:
int mon(const vector<int>& nums,int start,int end)
{
if(end == start+1) return nums[start];
else if(end == start+2) return max(nums[end-1],nums[start]);
vector<int> dp(end-start,0);
dp[0] = nums[start];
dp[1] = max(nums[start+1],nums[start]);
for(int ii =2;ii<end-start;ii++)
dp[ii] = max(dp[ii-1],dp[ii-2]+nums[start+ii]);
return dp[dp.size()-1];
}
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
if(nums.size() == 2) return max(nums[0],nums[1]);
int result1 = mon(nums,0,nums.size()-1);
int result2 = mon(nums,1,nums.size());
return max(result1,result2);
}
};
树形dp,对于树形的结构,我们对其进行遍历时,包括前序中序后序遍历,以及层次遍历。
对于这道题,我们访问一个节点时,需要孩子的信息,即孩子节点是否被偷了,孩子节点被偷的话其可以获得的最大金额是多少,没有被偷的话返回的最大金额是多少,对此,我们应该使用后序遍历,也就是先访问孩子后访问该节点。
而对于每个节点,我们往往需要的是左右孩子被偷以及不被偷的最大金额,因此,我们遍历每个节点时,都向上返回一个数组,其中记录被偷与不被偷的最大金额(下标0:表示被偷,下标1:表示不被偷。
访问节点时,我们也向上返回这个数组result,而result 的更新逻辑为:
如果被偷,则孩子不能被偷,因此我们让val0 = left[1]+right[1]+root->val;
如果不被偷,则孩子可以被偷,也可以不被偷,因为我们要获取最大金额,因此,我们从中取得孩子的最大值,即val1 = max(left[0],left[1])+max(right[0],right[1]);
最后向上返回这两个元素即可。
class Solution {
public:
int rob(TreeNode* root) {
if(root==nullptr) return 0;
vector<int> result = robtree(root);
return max(result[0],result[1]);
}
vector<int> robtree(TreeNode* root)//0偷 1不偷
{
if(root==nullptr) return{0,0};
vector<int> left = robtree(root->left);
vector<int> right = robtree(root->right);
int val0 = left[1]+right[1]+root->val;
int val1 = max(left[0],left[1])+max(right[0],right[1]);
return {val0,val1};
}
};