代码随想录算法训练营第四十七天|LeetCode198 打家劫舍、LeetCode123 打家劫舍II、LeetCode337 打家劫舍III

文章讲述了打家劫舍问题的三种变体(I,II,III),分别涉及线性数组、环形数组和二叉树的动态规划解决方案。重点在于理解dp数组的含义、递推公式的建立和如何处理首尾相邻的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

198.打家劫舍

思路:不能偷相邻的两家。首先确定dp数组及其下标含义 dp[i]表示i之前(包含i)可以投的最大金额。确定递推公式,当前dp[i]与前两个索引有关,如果偷当前节点 dp[i] = dp[i-2]+nums[i].如果不偷当前节点dp[i] = dp[i-1],所以dp[i] = max( dp[i-2]+nums[i], dp[i-1])。初始化dp数组,dp[0] = nums[0]. dp[1]=max(nums[0],nums[1]). 遍历顺序,从小到大。打印dp数组,可以用于debug。

class Solution {
public:
    int rob(vector<int>& nums) {
        //确定dp数组及其下标的含义 dp[i]表示i之前(包含i)能投的最大金额
        //递推公式 i当前与i前面的两个索引有关 dp[i] = max(dp[i-2]+value[i],dp[i-1])。即偷当前i和不偷当前i两种情况
        //初始化dp[0] = value[0]; dp[1] = max(value[0],value[1]).
        //遍历顺序,从小到大遍历
        //打印dp数组,用于debug
        if(nums.size()==0)
        {
            return 0;
        }
        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 j = 2;j<nums.size();j++)
        {
            dp[j] = max(dp[j-2]+nums[j],dp[j-1]);
        }
        return dp[nums.size()-1];
    }
};

213.打家劫舍II

思路:此时数组为一个环形,相比于打家劫舍I,需要考虑首位是否相邻偷取的情况,因此将环形结构展开成两个线性结构,一个包含头元素不包含尾元素,一个包含尾元素不包含头元素,这样一定不会首尾同时包含。选择两种情况的最大值。

class Solution {
public:
    int rob(vector<int>& nums) {
        //划分成两种情况 一种情况包含头元素不包含尾元素 一种情况包含尾元素不包含头元素
        //这样两种情况就不会首尾相邻
        vector<int> dp1(nums.size()-1,0);
        vector<int> dp2(nums.size()-1,0);
        if(nums.size()==1)
        {
            return nums[0];
        }
        if(nums.size()==2)
        {
            return max(nums[0],nums[1]);
        }
        dp1[0] = nums[0];
        dp1[1] = max(nums[0],nums[1]);
        for(int i =2;i<nums.size()-1;i++)
        {
            dp1[i] = max(dp1[i-2]+nums[i],dp1[i-1]);
        }
        dp2[0] = nums[1];
        dp2[1] = max(nums[1],nums[2]);
        for(int j = 2;j<nums.size()-1;j++)
        {
            dp2[j] = max(dp2[j-2]+nums[j+1],dp2[j-1]);
        }
        return max(dp1[nums.size()-2],dp2[nums.size()-2]);
    }
};

337.打家劫舍III

思路:这道题是二叉树dp问题,即利用了动态规划还需要利用递归二叉树。现确定递归三部曲,首先确定递归函数的输入参数和返回类型,然后确定终止条件,当节点为NULL时,返回{0,0}。第一个元素代表不偷这个节点的最大金额,第二个元素代表偷了这个节点后的最大金额。确定单层递归逻辑,后序遍历,左右中,如果偷当前节点,那么val1 = root->val+dpleft[0]+dpright[0],不能偷子节点。如果不偷当前节点,可以选择偷或者不偷子节点val2 = max(dpleft[0],deleft[1])+max(dpright[0]+dpright[1]). return {val2,val1};

/**
 * 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:
    //递归函数
    //递归三部曲 确定递归函数的参数和返回类型
    vector<int> traversal(TreeNode* root)
    {
        //确定终止条件
        if(root==NULL)
        {
            return {0,0};
        }
        //确定单层递归逻辑
        vector<int> leftdp = traversal(root->left);
        vector<int> rightdp = traversal(root->right);
        //偷当前节点
        int val1 = root->val+leftdp[0]+rightdp[0];
        //不偷当前节点
        int val2 = max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
        return {val2,val1};
    }

    int rob(TreeNode* root) {
        vector<int> result = traversal(root);
        return max(result[0],result[1]);
    }
};

收获:

打家劫舍的一天,dp数组的含义为包含当前节点(无论偷不偷)的最大金额,根据相邻的关系推出递推公式。

二叉树dp问题,既要考虑递归三部曲,又要考虑dp五部曲。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值