打家劫舍3
最开始我一看,就觉得这个不是分行求和吗?
就写了个dfs交上去了,当时还以为自己很对,想到了最简单的解法。
代码如下
class Solution {
public:
int odd, even;
int rob(TreeNode* root) {
dfs(root,1);
return max(odd, even);
}
void dfs(TreeNode* root, int level) {
if(!root) return;
if(level & 1) {
odd += root->val;
} else {
even += root->val;
}
dfs(root->left, level + 1);
dfs(root->right, level + 1);
}
};
结果4-2-1-3这个测试样例拦住我了。
如果你分奇偶求和,那么得到的结果是6,但是实际上 如果选择第一个和最后一个 那么可以得到最优的解7。
分行求和的问题在于,认为最优解在于尽可能多选取元素,而没有考虑元素之间的大小关系。
所以只好不情不愿地去想算法。(分行求和甚至能过一半样例)
然后想了会,就给出了下面这种解法。
写的时候就差不多知道了,会超时。
但是想看看这题的时间复杂度给的是多少,就交上去了。
卡在最后几个大规模数据了。
class Solution {
public:
int rob(TreeNode* root) {
if(!root) return 0;
int x = 0, y = 0;
//偷root
x += root->val;
if(root->left) {
x += rob(root->left->left);
x += rob(root->left->right);
}
if(root->right) {
x += rob(root->right->left);
x += rob(root->right->right);
}
//不偷root
y += rob(root->left);
y += rob(root->right);
return max(x, y);
}
};
上述代码会超时的问题的根源就是对于一个问题的重复求解。
比如在求解rob(root->left) 的时候 也会去求解 rob(root-left->right)
可是之前已经求解过了,于是很自然地想到去保存下这些值。
于是做了一个hash表,去记忆以该结点为根可偷到的最大值。
class Solution {
unordered_map<TreeNode*, int> sums;
public:
int rob(TreeNode* root) {
return robRoot(root);
}
int robRoot(TreeNode* root) {
//hash表记忆 O(N) 复杂度
if(!root) return 0;
if(sums.count(root)) return sums[root];
int x = root->val;
if(root->left) {
x += (robRoot(root->left->left) + robRoot(root->left->right));
}
if(root->right) {
x += (robRoot(root->right->left) + robRoot(root->right->right));
}
int y = robRoot(root->left) + robRoot(root->right);
sums[root] = max(x, y);
return sums[root];
}
};
由于之前做过打家劫舍1,2,我知道这题本质上是一个自底向上求解的动态规划。
但是递归自顶向下的方式和dp那种自底向上的方式不一样。
但是呢?反过来想一想,最后不断退栈的时候,其实就是自底向上的一个过程。
为了去利用bottom的计算结果,在具体的遍历形式上,应该采取后序遍历。
类似于之前的打家劫舍1,2,需要知道不偷当前点可获得的最大金额和偷这个点可获得的最大金额,并且作为返回值返回。
返回值多于1个,类型相同,用vector放一下就好了。
于是很自然的就有如下代码。
class Solution {
public:
//树形dp 利用vector作为返回值 保留两个信息 分别是 偷取这个点可获得的最大金额 与不偷取这个点可获得的最大金额
int rob(TreeNode* root) {
vector<int> ans = robRoot(root);
return max(ans[0],ans[1]);
}
vector<int> robRoot(TreeNode* root) {
if(!root) return {0,0};
//x 偷取当前点可获得的最大金额 y 不偷取当前点可获得的最大金额
int x = root->val, y = 0;
vector<int> left = robRoot(root->left);//偷取左子树的两个信息
vector<int> right = robRoot(root->right);
//既然已经偷了当前点了 那么必然不能选择 偷取子树的金额
//所以只能选择 不偷取左右子树可获得的最大金额
x += left[1] + right[1];
//没有偷当前点 偷不偷下一个结点不受影响 所以从两个里面选一个最大的
y += max(left[0], left[1]) + max(right[0], right[1]);
//返回两个值
return {x,y};
}
};