欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 【代码随想录day39】【C++复健】198.打家劫舍;213.打家劫舍II ;337.打家劫舍III

【代码随想录day39】【C++复健】198.打家劫舍;213.打家劫舍II ;337.打家劫舍III

2024/11/30 6:55:25 来源:https://blog.csdn.net/yumenohimiko/article/details/144010441  浏览:    关键词:【代码随想录day39】【C++复健】198.打家劫舍;213.打家劫舍II ;337.打家劫舍III

198.打家劫舍

本题别的地方都写出来了,不过在最后写的时候我没想明白dp[n-2]有没有可能比dp[n-1]大,写了个max(dp[n-1],dp[n-2])。但其实这个问题很简单,因为在dp数组里面dp[i]里取max的时候有一项就是dp[i-1],所以一定是大于等于的。
class Solution {
public:int rob(vector<int>& nums) {int n = nums.size();vector<int> dp(n);dp[0] = nums[0];if(n <= 1){return dp[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[n-1];}
};

213.打家劫舍II

本题老毛病又犯了,这两个dp数组可以用一个函数进行复用,然后只要输入不同的下标即可,但是我却自己实现了这两个dp数组,在传值和合法性判断上,写起来麻烦不少,不过好在能正常执行。

还有一个问题是不知道max函数只能比较两个值,也不知道不能传两个迭代器,如果要比较三个数就只能调用两次max函数了。

class Solution {
public:int rob(vector<int>& nums) {int n = nums.size();if(n <= 1){return nums[0];}vector<int> dp1(n-1);dp1[0] = nums[0];if(n <=2){return max(nums[0], nums[1]);}if(n <= 3){return max(nums[0], max(nums[1], nums[2]));}dp1[1] = max(nums[0], nums[1]);vector<int> dp2(n-1);dp2[0] = nums[1];dp2[1] = max(nums[1], nums[2]);for(int i=2; i<n-1; i++){dp1[i] = max(dp1[i-1], dp1[i-2] + nums[i]);}for(int i=3; i<n; i++){dp2[i-1] = max(dp2[i-2], dp2[i-3] + nums[i]);}return max(dp1[n-2], dp2[n-2]);}
};

337.打家劫舍III

这个打家劫舍的题也是二周目了,上一周目印象很深,所以本来以为能写对的,但还是在dp数组上面写错了。问题就出在了第二个向量,也就是不打劫根节点的时候的更新方式上面:

我写的更新代码如下:
int second = max(right[0] + left[0], right[1] + left[1]);

但实际正确的应该是:
int second = max(left[0], left[1]) + max(right[0], right[1]);

这两句之间差在哪了呢?差就差在,上面这个写法,左子树和右子树只能同时选择偷这个节点或不偷这个节点,但实际这个逻辑是不对的,当我选择不偷根节点的时候,左子树和右子树的偷或者不偷之间是完全没有关系的,它们之间应该是独立的关系,否则就会漏掉一些情况。

而底下这个写法才保证了左右之间的相互独立的关系。

此外再啰嗦一点,为什么这个时候,前后节点需要进行大小的比较?这里的比较有点类似上一道题里面最后的比较,因为0号位和1号位实际上取值的区间是不同的,所以它们之间的大小也没有绝对关系,当然要进行比较来取最大。而1里面因为存在包含关系,所以不比较也可以。

还有就是vector<int>,如果我想要定义一个vector,里面的0号位和1号位是两个0,那应该用大括号,而非小括号,小括号的vector<int> (0,0) 代表的是长度和初始值都为0的一个vector。

class Solution {
public:int rob(TreeNode* root) {if(!root){return 0;}vector<int> result = midgap(root);return max(result[0], result[1]);}vector<int> midgap(TreeNode* root){if(!root){return vector<int> {0,0};}vector<int> left = midgap(root->left);vector<int> right = midgap(root->right);int first = left[1] + right[1] + root->val;int second = max(left[0], left[1]) + max(right[0], right[1]);return vector<int> {first, second};}
};

最后附上GPT对于为什么三需要比较而一不需要的解释:

打家劫舍 III 中需要比较的原因:

打家劫舍 III 中,我们用一个二维的状态来表示每个节点的选择。对于每个节点,我们有两个可能的选择:

  1. 不偷当前节点,这意味着我们可以选择左右子树的最大收益,max(left[0], left[1]) + max(right[0], right[1])
  2. 偷当前节点,这时我们必须计算左右子树的“不偷”收益,即 left[0] + right[0] + root->val

由于 左右子树的选择是独立的,它们的最大值并不直接包含或依赖对方。因此,在 打家劫舍 III 中,我们需要分别比较左右子树中“偷或不偷”的收益,以便找到最优的方案。

打家劫舍 I 中的大小比较:

打家劫舍 I 中,问题是一维的,我们用 dp[i] 表示抢劫到第 i 个房子时的最大金额。状态转移方程是:

dp[i] = max(dp[i-1], dp[i-2] + nums[i]);

这里的比较,实际上是一个 互斥 的选择,因为如果你选择了抢劫第 i 个房子,你就不能抢劫第 i-1 个房子,反之亦然。这就是为什么我们需要进行比较来找出最大的收益,因为我们有两种互斥的选择(偷或不偷)。

为什么 0号位和1号位取值区间不同,且需要比较

打家劫舍 III 中,0号位1号位 表示两种不同的状态:

  • 0号位 表示“不偷当前节点”,它的值是左右子树的最大值(不包括当前节点的值)。
  • 1号位 表示“偷当前节点”,它的值是当前节点的值加上左右子树不偷的最大值。

这两个状态的取值区间是不同的,因为它们分别代表了两种完全不同的策略:

  • 不偷当前节点:你可以选择偷或者不偷左子树和右子树。
  • 偷当前节点:你只能选择不偷左子树和右子树。

这两个状态之间并没有包含关系,所以必须进行比较,以确定偷或不偷哪个更有利。

为什么在打家劫舍 I 中不需要比较

打家劫舍 I 中,状态转移是基于两个状态(抢劫当前房子或不抢劫),它们之间并没有独立性或者包含关系。直接用 max(dp[i-1], dp[i-2] + nums[i]) 就可以得到最大值。这是因为 dp[i-1]dp[i-2] 表示相邻房子的状态选择,两者的选择是互斥的,并且不需要考虑它们之间的包含关系。

结论:

  • 打家劫舍 III 中,需要比较左右子树的“偷或不偷”最大值,因为左右子树的选择是独立的,没有包含关系,必须独立选择。
  • 打家劫舍 I 中,不需要额外比较,因为每个房子的选择是基于前一个房子的状态,两个选择是互斥的,不需要考虑包含关系。

所以,你的理解是对的,打家劫舍 III 中的大小比较类似于 打家劫舍 I 中的比较,但原因不同,主要是 独立选择互斥选择 的问题。

希望这次解释能帮你更好地理解这个问题!如果有其他问题,随时问我。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com