LeetCode 337 House Robber III
题意
给一个二叉树,从中取任意多个数并使这些数的和最大。要求任意两个直接相连的节点不能同时取到。
思路
1 暴力深搜
时间空间复杂度都没有限制,爆搜来一发。首先,假如选了一个节点,那这个节点的直接孩子都不能选,只能选不直接相连的孩子,因此需要引入一个布尔值表示当前节点是否可选;其次对于每个节点,如果可选的话,我们也可以选择不选这个节点,所以大致流程如下:
dfs:
当前节点可以取?
取当前节点val
a = 求左孩子不可取时左子树的最大值
b = 求右孩子不可取时右子树的最大值
res = max(a + b + val, res)
不管当前节点可不可取,都可以选择不取:
a = 左孩子可取时左子树最大值
b = 右子树可取时右子树最大值
res = max(res, a + b)
return res
好了,submit~ == **时间3884ms **== 显然这么做没有任何意义(指数级别的时间复杂度,分分钟爆炸。。。),代码我就不贴了。
2 优化搜索
其实这个就是利用记忆式搜索的思想,将已经求过的结果记录下来,需要的时候直接用。实际上对于树来说,深搜是很容易出现重复访问某颗子树的情况。对于本题,每个节点有可取与不可取两个状态,因此可以采用map+pair
的方式保存搜索过的数据。当然如果你想采用数组的方式给每个节点编号,我劝你还是放弃吧,稀疏树很容易爆的。
代码如下
class Solution {
private:
map<int, pair<int, int>> dp;
int dfs(TreeNode *p, bool canRob, int k) {
if (p == nullptr)
return 0;
if (canRob && dp[k].first != -1)
return dp[k].first;
if (!canRob && dp[k].second != -1)
return dp[k].second;
int res = 0, lc = k << 1, rc = k << 1 | 1;
if (canRob) {
int rv = dfs(p->left, false, lc) + dfs(p->right, false, rc);
res = max(rv + p->val, res);
}
res = max(dfs(p->left, true, lc) + dfs(p->right, true, rc), res);
canRob ? dp[k].first = res : dp[k].second = res;
return res;
}
void getTreeSz(TreeNode *p, int k) {
if (p == nullptr)
return;
dp[k] = make_pair(-1, -1);
getTreeSz(p->left, k << 1);
getTreeSz(p->right, k << 1 | 1);
}
public:
int rob(TreeNode *root) {
getTreeSz(root, 1);
return dfs(root, true, 1);
}
}
当然实际上引入map
就又多了一个log级别的复杂度了,不过时间已经好很多了**?** 44ms (-:。
3 消除map
其实无需map,只要将往map里面存的数据存到节点里面就可以了。可节点是已经写好的,那不妨复制一下这个树,给新树节点中加一个属性,保存上面map里面保存的数据。至于复制树,方法很多,简单的深搜就可以实现。
代码
class Solution {
private:
int dfs2(TNode *p, bool canRob) {
if (p == nullptr)
return 0;
if (canRob && p->robValPair.first != -1)
return p->robValPair.first;
if (!canRob && p->robValPair.second != -1)
return p->robValPair.second;
int res = 0;
if (canRob) {
int rv = dfs2(p->left, false) + dfs2(p->right, false);
res = max(rv + p->val, res);
}
res = max(dfs2(p->left, true) + dfs2(p->right, true), res);
canRob ? p->robValPair.first = res : p->robValPair.second = res;
return res;
}
void cpTree(TreeNode *p, TNode *q) {
if (p == nullptr)
return;
q->val = p->val;
if (p->left != nullptr) {
q->left = new TNode(0);
cpTree(p->left, q->left);
}
if (p->right != nullptr) {
q->right = new TNode(0);
cpTree(p->right, q->right);
}
}
public:
int rob(TreeNode *root) {
TNode *q = new TNode(0);
cpTree(root, q);
return dfs2(q, true);
}
}
总结
本题实际上是一个从底向上的动态规划,讨论版中还介绍了一些其它的方法和思路,感兴趣的可以看看。上述代码应该是有许多可以优化的地方的,比如表示当前节点是否可取的状态canRob
,如果文中有不妥之处或者你有更好的方法想要分享,可以在评论区留言。