代码随想录算法训练营第四十八天|198.打家劫舍 、213.打家劫舍II、337.打家劫舍III

LeetCode 198.打家劫舍

链接:198.打家劫舍

思路:

动态规划经典题目,首先定义下标:dp[i]表示打劫i+1个房屋可以获得的最大金额。初始化dp数组为0,dp[0]=nums[0]表示打劫1个房屋可以获得nums[0]的金额。然后确认递推公式,根据题意,无法打劫两间相邻的房屋,所以如果打劫了nums[i-1]的房屋,就不能打劫nums[i],所以dp[i]就需要在打劫nums[i-1]的房屋和nums[i]的房屋两者之间选一个最大值就可以最大化金额。如果打劫nums[i-1]的房屋,可以获得的最大金额为dp[i-1],如果打劫nums[i]的房屋,可以获得的最大金额为dp[i-2]+nums[i],那么递推公式为dp[i] = max(dp[i-2] + nums[i], dp[i-1])。遍历顺序为从i=2开始向后遍历,注意因为递推公式里有i-2所以i必须大于等于2。同时别忘了初始化dp[1]为max(nums[0], nums[1]),当只有两间房屋的时候取金额大的那个。最后返回dp[nums.size()-1]就行了。

代码:

class Solution {
public:
    int rob(vector<int>& nums) {
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        if (nums.size() == 1)
            return dp[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++)
            dp[i] = max(dp[i-2] + nums[i], dp[i-1]);
        return dp[nums.size() - 1];
    }
};

LeetCode 213.打家劫舍II

链接:213.打家劫舍II

思路:

相比于上一道题增加了一个限制条件,就是数组头尾的两个数组也算相邻的两间房,所以需要在打劫头间房屋/打劫尾间房屋之间选择一个,如何用dp记录这两种情况的最佳选择呢?只要用两个dp数组就可以了,两个数组分别记录范围[0, nums.size() - 2]和[1, nums.size() - 1]的房间,分别代表了打劫头间房屋和打劫尾间房屋,大小都为nums.size() -1。

初始化dp1和dp2:

  1. dp1[0] = nums[0] 为第一间房间的金额
  2. dp1[1]  =  max(nums[0], nums[1]) 为第一间房间和第二间房间的最大金额
  3. dp2[0] = nums[1] 为第二间房间的金额
  4. dp2[1] = max(nums[1], nums[2]) 为第二间房间和第三间房间的最大金额

两个dp数组的递推公式都是和之前的一样,唯一的区别是nums的下标不一样,dp2始终比dp1取的下标要靠后一位。最后在dp1和dp2的最后一位取一个最大值,表示偷第一间/偷末尾间所能获得的最大金额。

代码:

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 1)
            return nums[0];
        if (nums.size() == 2)
            return max(nums[0], nums[1]);
        // 第一个dp数组,范围[0, nums.size() - 2]
        vector<int>dp1(nums.size()-1);
        // 第二个dp数组,范围[1, nums.size() - 1]
        vector<int>dp2(nums.size()-1);
        dp1[0] = nums[0];
        dp1[1] = max(nums[0], nums[1]);
        dp2[0] = nums[1];
        dp2[1] = max(nums[1], nums[2]);
        for (int i = 2; i < nums.size() - 1; i++)
        {
            dp1[i] = max(dp1[i-2] + nums[i], dp1[i-1]);
            dp2[i] = max(dp2[i-2] + nums[i + 1], dp2[i-1]);
        }
        return max(dp1[nums.size()-2], dp2[nums.size()-2]);
    }
};

LeetCode 337.打家劫舍III

链接:337.打家劫舍III

思路:

这一次,房间的排列不再是数组了,而是一个二叉树。动态规划的做法和之前的一样,都是采用在遍历的时候比较打劫这一间还是前一间所能获得的最大金额,然后取更大的那个。遍历二叉树的时候,一定要选取后序遍历,这样才能够比较方便的进行比较。

用于遍历的递归函数postorder需要返回一个dp数组,只有两个元素,用于储存打劫当前房屋和打劫前一间房屋所能获得的最大金额。当遇到空节点时,返回{0, 0},因为空节点代表没有房间,所能获得的金额为0。然后分别用left、right两个数组表示左节点和右节点最大金额,按照和之前一样的逻辑,可以写出递推公式

  • dp[0] = max(left[0], left[1]) + max(right[0], right[1]):表示不偷该节点,能获得的金钱为左节点偷或不偷能获得的最大金钱,加上右节点偷或不偷能获得的最大金钱
  • dp[1] = left[0] + right[0] + root->val:偷该节点,能获得的金钱为该节点金钱,加上不偷两个子节点能获得的最大金钱

然后将dp数组返回给上一个节点,最后在头节点取dp[0]和dp[1]中大的那个并返回,就是所能获取的最大金额。

代码:

/**
 * 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 {
public:
    int rob(TreeNode* root) {
        if (root == nullptr)
            return 0;
        vector<int> dp = postorder(root);
        return max(dp[0], dp[1]);
    }
    vector<int> postorder(TreeNode* root)
    {
        if (root == nullptr)
            return {0, 0};
        vector<int> left = postorder(root->left);
        vector<int> right = postorder(root->right);
        // dp[0]: 不偷该节点能得到的最大金钱
        // dp[1]: 偷该节点能得到的最大金钱
        vector<int> dp(2);
        // 不偷该节点,能获得的金钱为左节点偷或不偷能获得的最大金钱,加上右节点偷或不偷能获得的最大金钱
        dp[0] = max(left[0], left[1]) + max(right[0], right[1]);
        // 偷该节点,能获得的金钱为该节点金钱,加上不偷两个子节点能获得的最大金钱
        dp[1] = left[0] + right[0] + root->val;
        return dp;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值