代码随想录训练营第48天|198.打家劫舍、213.打家劫舍Ⅱ、337.打家劫舍Ⅲ

198.打家劫舍、213.打家劫舍Ⅱ、337.打家劫舍Ⅲ

198.打家劫舍

对于打家劫舍的问题,我们可以利用动态规划,利用一个数组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]);
    }
};

213.打家劫舍Ⅱ

相对于打家劫舍,这道题多了一项要求,就是不能连续偷第一家和最后一家,因此,为了防止头元素与尾元素同时被包含,我们可以将元素的首元素和尾元素分别去掉,然后就变成了普通的打家劫舍问题,我们分别进行打家劫舍的求解,最后求两次解的最大值,也就是最终的答案。
对此,我们可以写一个函数,进行打家劫舍的求解,此函数可以用上述的两种方法的任意一种。

函数

    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);
    }
};

337.打家劫舍Ⅲ

树形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};
    }
};

你可能感兴趣的:(leetcode,算法,动态规划)