LeetCode 337. House Robber III 解题报告
题目描述
The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.
Determine the maximum amount of money the thief can rob tonight without alerting the police.
示例
Example 1:
3
/ \
2 3
\ \
3 1
Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
Example 2:
3
/ \
4 5
/ \ \
1 3 1
Maximum amount of money the thief can rob = 4 + 5 = 9.
限制条件
没有明确给出.
解题思路
我的思路:
对于每一个节点,都有两种情况,偷或不偷。对应这两种情况,可以设置两个变量,分别存储偷与不偷的总金额。自然,必须遍历所有的节点,由于我希望的是能够从叶子节点开始处理,一直往上到根节点,所以采用的是递归的方式,为了方便递归,实现时使用的是一个数组,数组的第一个元素存储偷当前节点时获得的总金额,数组第二个元素存储不偷当前节点时获得的总金额。
在一次递归中,我们需要进行的操作就是更新数组的两个变量并且返回该数组,递归的结束条件自然是当前节点为空,那直接返回数组[0, 0]。
if (!root)
return vector<int>(2,0);
由于题目说了不能连续偷直接相连的两个节点,因此,偷了当前节点,就不能偷左右子节点,所以很自然地,我们会写出如下的逻辑:
偷当前节点的总金额 = 当前节点的金额 + 不偷左子节点的金额 + 不偷右子节点的金额
不偷当前节点的金额 = 偷左子节点的金额 + 偷右子节点的金额
用代码表示,就会是:
vector<int> sum(2,0);
sum[0] += root->val + left[1] + right[1];
sum[1] += left[0] + right[0];
其中sum, left, right都是数组,存储着当前节点,左子节点,右子节点的两种偷窃情况的金额,第一个元素是偷的情况,第二个元素是不偷的情况。
上面的处理解决了这道题的第一个难点,把节点的相互联系给断开,变成只需考虑当前节点的情况,而不再需要考虑当前节点对下一个节点的影响。
目前我们的实现就会是下面那样:
vector<int> robNode(TreeNode* root) {
if (!root)
return vector<int>(2,0);
vector<int> left = robNode(root->left);
vector<int> right = robNode(root->right);
vector<int> sum(2,0);
sum[0] += root->val + left[1] + right[1];
sum[1] += left[0] + right[0];
return sum;
}
然而当提交时会发现是wrong answer。对于
2
/ \
1 3
\
4
这个测试用例,我们上面的代码返回的是6(偷2和4),而正确的答案应该是7(偷3和4)。那问题出现在哪里?这就是这道题第二个难点,在进行偷窃情况的更新时,我们陷入了一个误区:不偷当前节点时,我们默认去偷左右子节点。
上面的例子中,我们不偷2时,我们就去偷了1和3得到金额4,而金额4比偷2跟4得到的金额6小,所以我们返回了6。然而我们不偷2了,为什么一定要去偷左右子节点?如果孙子节点更大,我们应该做的是去偷孙子节点,而不是偷子节点!所以,不偷当前节点时,我们应该是选择左右子节点中金额更大的偷窃选项。比如上例中,不偷2时,左子节点的情况是[1,4],我们选择的应该是4(表示不偷左子节点,但是偷孙子节点,因为它获得的金额更大)。
所以正确的更新代码应该是:
vector<int> sum(2,0);
sum[0] += root->val + left[1] + right[1];
sum[1] += max(left[0], left[1]) + max(right[0], right[1]);
到了这里,这道题就完成了。
看了Discuss,发现大神的代码跟我的一样,虽然我完全没有想到是动态规划(⊙﹏⊙)b。其中某位大神给出了他整个思维过程Step by step tackling of the problem,可以去学一下怎么从原始的想法思考得出更优的解法。
代码
我的代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {
vector<int> res = robNode(root);
return max(res[0], res[1]);
}
vector<int> robNode(TreeNode* root) {
if (!root)
return vector<int>(2,0);
vector<int> left = robNode(root->left);
vector<int> right = robNode(root->right);
vector<int> sum(2,0);
sum[0] += root->val + left[1] + right[1];
sum[1] += max(left[0], left[1]) + max(right[0], right[1]);
return sum;
}
};
总结
这道题我是碰巧用对了实现方式,不然估计想半天也想不出来,因为动态规划,着实不怎么学过。不过看了大神的思考,以及结合自己做题的过程,还是有一些自己关于动态递归的感悟。
终于填好了今天的坑,周末就是想懒散一些,不过要坚持,不能太放松,继续加油!填坑之旅还很长的路要走~
本文针对LeetCode 337号问题“打家劫舍III”进行了深入解析,通过递归和动态规划的方法,详细阐述了解题思路,并提供了一段完整的C++代码实现。
8万+

被折叠的 条评论
为什么被折叠?



