打家劫舍3

本文介绍了如何解决一道关于二叉树的打家劫舍问题,最初通过分行求和的思路遇到困难,后采用动态规划和记忆化搜索的方法优化解法。博主首先尝试了深度优先搜索,然后意识到问题的关键在于考虑节点间的大小关系,避免了重复计算并使用哈希表存储中间结果。最终,通过后序遍历实现了自底向上的动态规划解决方案,解决了超时问题。
摘要由CSDN通过智能技术生成

打家劫舍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};
    }
};

dp专题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值