侧边栏壁纸
博主头像
thinkTV博主等级

喜爱动漫的二刺螈一枚,摩托车云爱好者(快要有车了)。 懂一点技术的在读生物医学工程研究生( •̀ ω •́ )✧,多多指教。

  • 累计撰写 127 篇文章
  • 累计创建 17 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

代码随想录算法训练营第四十八天 | 打家劫舍

thinkTV
2023-06-05 / 0 评论 / 0 点赞 / 80 阅读 / 1,318 字 / 正在检测是否收录...

1. 打家劫舍Ⅰ

代码随想录:原文

力扣题目:198.打家劫舍

1.1 思路

当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
动规五部曲

  1. 确定dp数组(dp table)以及下标的含义
  • dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]
  1. 确定递推公式
  • 决定dp[i]的因素就是第i房间偷还是不偷
  • 如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i]
  • 如果不偷第i房间,那么dp[i] = dp[i - 1],即考 虑i-1房
  • 然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
  1. dp数组如何初始化
  • dp[0] 一定是 nums[0]
  • dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1])
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
  1. 确定遍历顺序
  • dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历
for (int i = 2; i < nums.size(); i++) {
    dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
  1. 举例推导dp数组

输入[2,7,9,3,1]为例:

图片-1

代码实现

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        vector<int> dp(nums.size());
        dp[0] = nums[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];
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

2. 打家劫舍Ⅱ

代码随想录:原文

力扣题目:213.打家劫舍II

2.1 思路

这道题目可以套用198.打家劫舍的思路,唯一区别就是成环了

情况一:考虑不包含首尾元素

图片-2

情况二:考虑包含首元素,不包含尾元素

图片-3

情况三:考虑包含尾元素,不包含首元素

图片-4

而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了

情况二 和 情况三分别调用 的方法函数

2.2 代码实现

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        int result1 = robRange(nums, 0, nums.size() - 2); // 情况二
        int result2 = robRange(nums, 1, nums.size() - 1); // 情况三
        return max(result1, result2);
    }
    // 198.打家劫舍的逻辑
    int robRange(vector<int>& nums, int start, int end) {
        if (end == start) return nums[start];
        vector<int> dp(nums.size());
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[end];
    }
};

3. 打家劫舍Ⅲ

代码随想录:原文

力扣题目:213.打家劫舍Ⅲ

3.1 思路

  • 本题一定是要后序遍历,因为通过递归函数的返回值来做下一步计算
  • 如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子

递归三部曲(框架)融合动规五部曲

  1. 确定递归函数的参数和返回值
  • 需要一个节点 偷与不偷的两个状态所得到的金钱
  • 返回值就是一个长度为2的数组
vector<int> robTree(TreeNode* cur) {
  • 返回数组就是dp数组
  • p数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱
  • 本题dp数组就是一个长度为2的数组
  1. 确定终止条件
  • 如果遇到空节点的话,无论偷还是不偷都是0
if (cur == NULL) return vector<int>{0, 0};
  1. 确定遍历顺序
  • 后序遍历
  • 通过递归左节点,得到左节点偷与不偷的金钱
  • 通过递归右节点,得到右节点偷与不偷的金钱
  1. 确定单层递归的逻辑
  • 如果是偷当前节点,那么左右孩子就不能偷:val1 = cur->val + left[0] + right[0];
  • 如果不偷当前节点,那么左右孩子就可以偷:val2 = max(left[0], left[1]) + max(right[0], right[1]);
  • 最后当前节点的状态就是{val2, val1}; 不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右

// 偷cur
int val1 = cur->val + left[0] + right[0];
// 不偷cur
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
return {val2, val1};
  1. 举例推导dp数组

图片-5

3.2 代码实现

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]);
    }
    // 长度为2的数组,0:不偷,1:偷
    vector<int> robTree(TreeNode* cur) {
        if (cur == NULL) return vector<int>{0, 0};
        vector<int> left = robTree(cur->left);
        vector<int> right = robTree(cur->right);
        // 偷cur,那么就不能偷左右节点。
        int val1 = cur->val + left[0] + right[0];
        // 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);
        return {val2, val1};
    }
};

0

评论区