LeetCode 337 House Robber III

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,如果文中有不妥之处或者你有更好的方法想要分享,可以在评论区留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值