【dp】LeetCode打家劫舍系列

198. 打家劫舍

在这里插入图片描述

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

  • dp[i]表示偷了 i 家时,获得的最大收益
  • nums[i]表示当前准备偷的房屋有多少钱

理解下来就是:偷了 i 家后,我所获得的最大收益要么等于偷完上一家的收益(不偷当前这家),要么等于偷完i-2家和当前房屋的金额之和(偷当前这家)

int rob(vector<int>& nums) {
        if(nums.size() == 0) return 0;//没有房屋
        if(nums.size() == 1) return nums[0];//只有一间房屋
        int len = nums.size();
        vector<int> dp(len);
        dp[0] = nums[0];//偷了第0家
        for(int i=1; i<len; i++){
            if(i == 1){
                dp[i] = max(nums[i] , dp[i-1]);//偷第1家
            }else{
                dp[i] = max(dp[i-2] + nums[i] , dp[i-1]);
            }
        }
        return dp[len-1];
    }

优化一下,可以去掉dp数组。由于dp数组只是记录了当前获取的最大收益,找个变量记录一下即可。当然上述代码中的dp[i-2]也得用变量记录

int rob(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        if(nums.size() == 2) return nums[0] > nums[1] ? nums[0] : nums[1];//只有两个房屋时
        int len = nums.size();
        int cur_profit;
        int pre_2 = nums[0];//dp[i-2]
        int pre_1 = nums[0] > nums[1] ? nums[0] : nums[1];//dp[i-1]
        for(int i=2; i<len; i++){
            cur_profit = max(pre_2 + nums[i] , pre_1);
            pre_2 = pre_1;
            pre_1 = cur_profit;
        }
        return cur_profit;
    }

213. 打家劫舍 II

在这里插入图片描述
分成两个区间 [0, n-2] [1, n-1] 偷即可

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

    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 1) return nums[0];
        return max(rob(nums, 0, n - 2), rob(nums, 1, n - 1));
    }
};

337. 打家劫舍 III

在这里插入图片描述

树的问题,很多时候采用 后序遍历:先处理左右孩子结点,再处理当前结点,整个过程看起来就像一层一层向上汇报信息

第 1 步:状态定义

dp[node][j] :这里 node 表示一个结点,以 node 为根结点的树,并且规定了 node 是否偷取能够获得的最大价值

  • j = 0 表示 node 结点不偷取;
  • j = 1 表示 node 结点偷取

第 2 步: 推导状态转移方程

根据当前结点偷或者不偷,就决定了需要从哪些子结点里的对应的状态转移过来。

  • 如果当前结点不偷,左右子结点偷或者不偷都行,选最大者;
  • 如果当前结点偷,左右子结点均不能偷。

第 3 步: 初始化

一个结点都没有,空节点,返回 0,对应后序遍历时候的递归终止条件;

第 4 步: 输出

在根结点的时候,返回两个状态的较大者。

class Solution {
public:
    unordered_map<TreeNode*, vector<int>> dp;

    void dfs(TreeNode* root){
        if(root == nullptr) return ;

        dfs(root->left);
        dfs(root->right);

        dp[root] = { 0, 0 };
        // if(root->left == nullptr && root->right == nullptr) {
        //     // 叶节点
        //     dp[root][0] = 0;
        //     dp[root][1] = root->val;
        //     return;
        // }
        dp[root][0] = max(dp[root->left][0], dp[root->left][1]) + max(dp[root->right][0], dp[root->right][1]);
        dp[root][1] = root->val + dp[root->left][0] + dp[root->right][0];
    }

    int rob(TreeNode* root) {
        // 当前节点偷/不偷,能获得的最大收益
        dp[nullptr] = {0, 0};
        dfs(root);
        return max(dp[root][0], dp[root][1]);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bugcoder-9905

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

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

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

打赏作者

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

抵扣说明:

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

余额充值