动态规划----解决“打家劫舍”问题

CSDN话题挑战赛第2期
参赛话题:面试宝典

  • 序:下面题解代码仅用了Java和c++两种语言进行了编写,算法你懂得了那个思想,哪种语言编写都一样。

198.打家劫舍

题目:

 浏览完题目(抓关键字):

房屋内的金额是非负整数;小偷不能同时偷取俩相邻的房间;最高金额(题目输出,也是题目所求);再看输入是一个各个房间所存在的金额序列,加上输出是最高金额。有序列,要求最...我们可以考虑用动态规划解题。

想到动态规划解决这题后,就开始动态规划三步走了:

1. 定义dp数组含义:

dp[i]元素值表示从头号房开始偷到 i 号房可以偷到的最大金额

2. 根据题目给出的属性写出递推公式(也就是根据题目所给的属性和dp数组i前的元素推出dp[i] ):

我们需要考虑的无疑就两种情况,偷 i 号房里的钱,不偷 i 房里的钱。

(记得dp数组的自己定义的含义)

情况1:偷 i 房里的钱,那 dp[i] = dp[i-2] + nums[i]

情况2:不偷 i 房里的钱,那 dp[i] = dp[i-1] (不偷的话可以偷到的最大金额当然是dp[i-1])

递推公式就是把这俩情况总结成一公式:

dp[i] = max(dp[i-1],dp[i-2] + nums[i])

3. 对dp数组进行初始化

这题我们需要初始化dp[0] 与 dp[1](所以输入的序列长度应该大于或等于2,记得序列为1或2时自身去返回其最大值)。

由于索引0前没元素了:dp[0] = nums[0]

由于索引不可以为负数:dp[1] = max(nums[1],dp[0])

而后就可以写解决该题的代码了。

C++版:

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1)
        return nums[0];
        else if(nums.size()==2)
        return max(nums[0],nums[1]);

        vector<int> dp(nums.size(),0);//该容器内元素表示从头号房开始偷到这偷到的最大金额
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);
        for(int i=2;i<nums.size();++i){
            dp[i] = dp[i-2] + nums[i];
            dp[i] = max(dp[i],dp[i-1]);
        }
        return dp[nums.size()-1];
    }
};

Java版:

class Solution {
    public int rob(int[] nums) {
        if(nums.length==1)
        return nums[0];
        else if(nums.length==2)
        return Math.max(nums[0],nums[1]);

        int[] dp = new int[nums.length];//该容器内元素表示从头号房开始偷到i偷到的最大金额
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        for(int i=2;i<nums.length;++i){
            dp[i] = dp[i-2] + nums[i];
            dp[i] = Math.max(dp[i],dp[i-1]);
        }
        return dp[nums.length-1];
    }
}

213.打家劫舍 ||

题目:

 浏览题目:这题相比第一题房子排布变成了一个圈,也就是第一个房间和最后一个房间是相邻的,其余关系属性啥的没变。

既然和第一题有关,那我们可以想办法转化成第一题那样可以用动态规划解决的题型。

怎么转化呢?可以通过切开首尾这样俩者就不会相邻了,就可以转化成上一题的逻辑解题了。

三种切法:

第一种(把首尾房间都去除):

第二种(把首房间去除):

 

第三种(把尾房间去除):

 

 由图可以看出第二种和第三种是包含了第一种的情况的,所以不用考虑,该题就转化俩种情况,以第一题的逻辑解法就可以了。

代码如下:

C++版:

class Solution {
private:
    //打家劫舍求所偷最大金额
    int uprob(vector<int> nums){
        if(nums.size()==1)
        return nums[0];
        vector<int> dp(nums.size(),0);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);
        for(int i=2;i<nums.size();++i){
            dp[i] = max(dp[i-1],nums[i]+dp[i-2]);
        }
        return dp[nums.size()-1];
    }
public:
    int rob(vector<int>& nums) {
        if(nums.size()==1)
        return nums[0];
        int situation2 = uprob(vector<int>(nums.begin()+1,nums.end()));//考虑情况二
        int situation3 = uprob(vector<int>(nums.begin(),nums.end()-1));//考虑情况三
        return max(situation2,situation3);//两种情况的最大的就是偷的最大金额了
    }
};

Java版:

class Solution {
    public int rob(int[] nums) {
        if(nums.length==1)
        return nums[0];
        //情况2
        int[] dp2 = new int[nums.length-1];
        //长度为1的话只能偷最后一个房间
        if(dp2.length==1)
        dp2[dp2.length-1] = nums[1];
        else{
        dp2[0] = nums[1];
        dp2[1] = Math.max(nums[2],nums[1]);
        for(int i=3;i<nums.length;++i){
            dp2[i-1] = Math.max(dp2[i-2],dp2[i-3]+nums[i]); 
        }
        }
         //情况3
        int[] dp3 = new int[nums.length-1];
        //长度为1的话只能偷第一个房间
        if(dp3.length==1)
        dp3[dp3.length-1] = nums[0];
        else{
        dp3[0] = nums[0];
        dp3[1] = Math.max(nums[0],nums[1]);
        for(int i=2;i<nums.length-1;++i){
            dp3[i] = Math.max(dp3[i-1],dp3[i-2]+nums[i]); 
        }
        }
        //情况2和情况3取最大值
        return Math.max(dp2[dp2.length-1],dp3[dp3.length-1]);

    }
}

337.打家劫舍 |||

题目:

 (虽然这个小区排布像二叉树似的,但并不影响小偷去偷money)

这题仍然可以用题一的解题思路去解。

还是两种情况,要么就抢这个结点,要么就步抢。抢这个结点的话,就不考虑抢左右孩子的结点;不抢该结点的话,就考虑抢左右孩子的结点。抢与不抢看哪种可以偷到更多的钱。

代码如下:

C++版:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    //记录访问过了的结点
    unordered_map<TreeNode*,int> mp;
public:
    int rob(TreeNode* root) {
        if(!root)
        return 0;
        //没有子孩子只能偷该结点
        if(!root->left&&!root->right)
        return root->val;
        //如果这个结点刚问过了那就直接返回就行了
        if(mp[root])
        return mp[root];
        //抢该结点
        int val1 = root->val;
        if(root->left) val1 += rob(root->left->left) + rob(root->left->right);
        if(root->right) val1 += rob(root->right->left) + rob(root->right->right);
        //不抢该结点
        int val2 = rob(root->left) + rob(root->right);
        mp[root] = max(val1,val2);
        return mp[root];
    }
};

今日励志:

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假正经的小柴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值