您现在的位置是:首页 >其他 >打家劫舍-代码随想录-刷题笔记网站首页其他
打家劫舍-代码随想录-刷题笔记
198. 打家劫舍
当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
 当前状态和前面状态会有一种依赖关系,那么这种依赖关系都是动规的递推公式。
 1)dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
 2)递推公式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])
 3)初始化:
 dp[0]=nums[0]
 dp[1]=max(nums[0],nums[1])
 4)顺序:从前到后
 5)举例说明
 [1,2,3,1]
 dp[0]=1
 dp[1]=2
 dp[2]=dp[0]+nums[2]=1+3=4
 dp[3]=4
//时间复杂度: O(n)
//空间复杂度: O(n)
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];
    }
};
 
213.打家劫舍II:成环了
1)dp[i]:以i为下标的数组被偷的最大金额是dp[i]
 2)递推公式
 dp[i]=max(dp[i-1],dp[i-2]+nums[i])
 3)初始化
 dp[0]=nums[0]
 dp[1]=max(nums[0],nums[1])
 4)顺序
 从前到后
 5)举例
 [2,3,2]
 dp[0]=2
 dp[1]=3
 dp[2]=3
 
 注意我这里用的是"考虑",例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三,取nums[1] 和 nums[3]就是最大的。
 而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了。
// 注意注释中的情况二情况三,以及把198.打家劫舍的代码抽离出来了
//时间复杂度: O(n)
//空间复杂度: O(n)
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];
    }
};
 
337.打家劫舍 III
本题一定是要后序遍历,因为通过递归函数的返回值来做下一步计算。
 关键是要讨论当前节点抢还是不抢。如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子
暴力递归
//时间复杂度:O(n^2),这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
//空间复杂度:O(log n),算上递推系统栈的空间
class Solution {
public:
    int rob(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right == NULL) return root->val;
        // 偷父节点
        int val1 = root->val;
        if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳过root->left,相当于不考虑左孩子了
        if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳过root->right,相当于不考虑右孩子了
        // 不偷父节点
        int val2 = rob(root->left) + rob(root->right); // 考虑root的左右孩子
        return max(val1, val2);
    }
};
 
记忆化递推
所以可以使用一个map把计算过的结果保存一下,这样如果计算过孙子了,那么计算孩子的时候可以复用孙子节点的结果。
//时间复杂度:O(n)
//空间复杂度:O(log n),算上递推系统栈的空间
动态规划
class Solution {
public:
    unordered_map<TreeNode* , int> umap; // 记录计算过的结果
    int rob(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right == NULL) return root->val;
        if (umap[root]) return umap[root]; // 如果umap里已经有记录则直接返回
        // 偷父节点
        int val1 = root->val;
        if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳过root->left
        if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳过root->right
        // 不偷父节点
        int val2 = rob(root->left) + rob(root->right); // 考虑root的左右孩子
        umap[root] = max(val1, val2); // umap记录一下结果
        return max(val1, val2);
    }
};
 
动态规划
- 确定递归函数的参数和返回值
 
这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组
 所以dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
 2.确定终止条件
 这也相当于dp数组的初始化
if (cur == NULL) return vector<int>{0, 0};
 
3.确定遍历顺序
 首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。
 通过递归左节点,得到左节点偷与不偷的金钱。
 通过递归右节点,得到右节点偷与不偷的金钱。
4.确定单层递归的逻辑
 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; (如果对下标含义不理解就再回顾一下dp数组的含义)
 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以: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};
 
5.举例推导dp数组
 以示例1为例,dp数组状态如下:(注意用后序遍历的方式推导)
 
整体代码
//时间复杂度:O(n),每个节点只遍历了一次
//空间复杂度:O(log n),算上递推系统栈的空间
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};
    }
};
                
            




U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结