算法学习 | day41/60 打家截舍/打家劫舍II/打家劫舍III

一、题目打卡

        1.1 打家截舍

        题目链接:198. 打家劫舍 - 力扣(LeetCode)

class Solution {
public:
    int rob(vector<int>& nums) {
        // 题目条件里面从 1 开始的
        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], dp[i - 2] + nums[i]); //不偷和偷
        }
        return dp[nums.size() - 1];
    }
};

        感觉如果从之前的背包问题过过渡到这个题目的话,就不是很难理解,状态变量——从0 到 i 所得到的最大价值,递推公式,是否选择索引所对应的价值,和背包问题很类似,题目的原型感觉比背包问题更好理解,不够题目应该还有不同的变种,继续看还会有什么样的变化。

        1.2 打家截舍II

        题目链接:​​​​​​​. - 力扣(LeetCode)

class Solution {
public:
    int get_max(vector<int> &nums){
        int res = nums[0];
        for(int i = 0 ; i < nums.size() ; i++){
            res = max(nums[i],res);
        }
        return res;
    }
    int rob(vector<int>& nums) {
        if(nums.size() <= 2) return get_max(nums); // 因为是一个环,所以只能取一个
        vector<int> dp1(nums.size() - 1, 0); // 存储不包含最后一个的时候的最大价值
        vector<int> dp2(nums.size() - 1, 0); // 存储不包含第一个的最大价值

        dp1[0] = nums[0];
        dp2[0] = nums[1];
        dp1[1] = max(nums[0],nums[1]);
        dp2[1] = max(nums[1],nums[2]);

        // for(int i = 2 ; i < nums.size() - 1 ; i++){
        //     dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
        // }

        // for(int i = 2 ; i < nums.size() - 1; i++){
        //     dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i + 1]);
        // }

        // 优化一下
        for(int i = 2 ; i < nums.size() - 1 ; i++){
            dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
            dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i + 1]);
        }

        return max(dp1.back(), dp2.back());


        // for(int i = 3 ; i < nums.size() ; i++){
        //     if(i <= nums.size() - 1) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        //     if(i >= 4) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        // }
    }
};

        这个题目主要是处理环这种结构,其实本质上来说,环也就是需要对首位进行区分,因为这两者不能同时被纳入 dp 数组的选择考虑(注意这里只是考虑,和选不选择没有关系,如果考虑是否选择就会陷入误区),所以也就是分成了两种情况,选择首项纳入考虑和选择尾项纳入考虑,这样分别对应了两个循环(其实也可以合并成为一个)。

        在合并的过程中,需要对遍历过程中 i 的意义理解清楚,这里的 i 指的是dp数组的索引,所以对应nums的时候,如果不取第一个,就需要做 +1处理。

        1.3 打家截舍III

        题目链接:. - 力扣(LeetCode)

/**
 * 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 get_length(TreeNode* root){
    //     if(!root) return 0;
    //     return get_length(root->left) + get_length(root->right) + 1;
    // }
    // void recur(TreeNode* root, vector<int> &dp, int i){
    //     if(!root) return;

    //     recur(root->left, dp, i + 1);
    //     dp[i] = max(dp[i - 1], dp[i - 2] + root->val);
    //     recur(root->right, dp, i + 1);
    // }
    // int rob(TreeNode* root) {
    //     // int length = 0;
    //     // length = get_length(root);
    //     // cout << length << endl;
    //     // if(length <= 1) return root->val;

    //     // vector<int> dp(length, 0);

    //     // // dp[0] = root->left ? root->left->val : root->val;
    //     // TreeNode* tmp = root;
    //     // while(tmp){
    //     //     dp[0] = root->val;
    //     //     tmp = tmp->left;
    //     // }
    //     // dp[1] = root->left ? (max(root->left->val, root->val)) : (max(root->val, root->right->val));
    //     // recur(root, dp, 2);
    //     // for(int i = 0 ; i < length; i++){
    //     //     cout << dp[i] << " ";
    //     // }
    //     // return dp[length - 1];
    // }

    // void tree2vector(vector<int> &nums, TreeNode* root){
    //     if(!root) return;

    //     tree2vector(nums, root->left);
    //     nums.push_back(root->val);
    //     tree2vector(nums, root->right);
    // }

    // int rob(TreeNode* root) {
    //     vector<int> nums;
    //     tree2vector(nums, root);

    //     for(int i = 0 ; i < nums.size() ; i++){
    //         cout << nums[i] << " ";
    //     }

    //     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], dp[i - 2] + nums[i]);
    //     }

    //     return dp.back();
    // }

    // pair<int,int> recur(TreeNode* root){ // 用前一位存储之前概念的 dp[i -1] 第二位存储 dp[i - 2]
    //     if(!root) return make_pair(0,0);

    //     pair<int,int> res1 = recur(root->left);
    //     pair<int,int> res2 = recur(root->right);

    //     pair<int,int> res = make_pair(0,0);
    //     res.first = max(res1.first + res2.first, res1.second + res2.second + root->val);
    //     res.second = min(res1.first + res2.first, res1.second + res2.second + root->val);

    //     cout << res.first << " " << res.second << endl;
    //     cout << "---------------" << endl;
    //     return res;
        // int tmp = res1.first;
        // res1.first = max(res1.first + res2.first, root->val + res1.second + res2.second);
        // res1.second = tmp;

        // int tmp1 = res2.first;
        // res2.first = max(res1.first + res2.first, root->val + res1.second + res2.second);
        // res2.second = tmp1;
        // return make_pair(max(res1.first + res2.first, root->val + res1.second + res2.second), );
    // }

    // int rob(TreeNode* root) {
    //     // pair<int, int> res = recur(root);
    //     // return res.first;

    //     // if(!root) return 0;

    //     // int res1 = rob(root->left);
    //     // int res2 = rob(root->right);
    //     // cout << res1 << " " << res2 <<endl;
    //     // cout << "-----------" << endl;
    //     // return max(res1 + res2, root->val);
    // }


    vector<int> recur(TreeNode* root){
        if(!root) return vector<int>{0,0}; // 抢当前节点获得的最大值和不抢当前节点获得的最大值

        vector<int> res1 = recur(root->left);
        vector<int> res2 = recur(root->right);

        int tmp1 = res1[1] + res2[1] + root->val; // 抢当前节点
        // 因为不抢这个节点的话,那子节点就有自己选择的权利是否抢
        int tmp2 = max(res1[0], res1[1]) + max(res2[0], res2[1]); // 不抢当前节点
        return vector<int>{tmp1,tmp2};
    }
    int rob(TreeNode* root) {

        vector<int> res = recur(root);
        return max(res[0],res[1]);
    }
};

        这个题目我做的过程,很多其实想明白了,但是没有写出来,最后发现原因就是对于状态的定义没有认知清楚,首先我想到了这个返回的值,肯定需要用两个数来表示,这样才能表征 1.1 和 1.2 中的 i - 1 和 i - 2, 但是我发现我递归记录的方法不太正确,如果按照我这个思路,正确的写法应该是这样的:

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

        这样的话,其实选择当前节点的过程就是前序遍历,不选择当前节点,因为要处理返回值,所以就是后序遍历。

        转换到dp方法,其实思路很类似,只是这里处理选择当前节点的时候,这里其实用了一小步贪心的思想,也就是无论如何都要取到最大,不管子节点是怎么考虑的,这其实也对应了上面递归方法中比较的过程,这也算是第一次接触了树形dp。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值