337. 打家劫舍 III

337. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额

示例 1:

img

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

img

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

提示:

  • 树的节点数在 [1, 104] 范围内
  • 0 <= Node.val <= 104

​ 经典的打家劫舍问题,我觉得更偏向于动态规划,这类问题只要寻找[状态]和[选择]即可,每家要么就抢要么是不抢,同时对于一个点,有时候总会有不同的选择方法走到这个点,如果有备忘录,就可以大大优化效率

class Solution {
public:
    //备忘录 记录每次的节点做出的选择
    unordered_map<TreeNode* ,int> memo;
    int rob(TreeNode* root) {
        if(root==nullptr)return 0;
        //该节点是否做过选择
        if(memo.find(root)!=memo.end()){
            return memo.at(root);
        }
        //抢这一家,抢的下一家就不是相连的
        int do_it=root->val+(root->left==nullptr?0:rob(root->left->left)+rob(root->left->right))
                            +(root->right==nullptr?0:rob(root->right->right)+rob(root->right->left));
        //不抢这一家 就可以去相连的选择
        int not_do_it=rob(root->left)+rob(root->right);
        //取最大值
        int res=max(do_it,not_do_it);
        //记录下选择过的节点
        memo.emplace(root,res);
        return res;
    }
};

说到打家劫舍,就顺手复习一下这一系列的问题,也是参考的《labuladong的算法小抄》。

经典动态规划:打家劫舍系列问题

House Robber I

图片

假想你就是这个专业强盗,从左到右走过这一排房子,在每间房子前都有两种选择:抢或者不抢。

如果你抢了这间房子,那么你肯定不能抢相邻的下一间房子了,只能从下下间房子开始做选择。

如果你不抢这间房子,那么你可以走到下一间房子前,继续做选择。

当你走过了最后一间房子后,你就没得抢了,能抢到的钱显然是 0(base case)。

以上的逻辑很简单吧,其实已经明确了「状态」和「选择」:你面前房子的索引就是状态,抢和不抢就是选择

备忘录出现的原因

就可以发现对于同一start位置,是存在重叠子问题的,比如下图:

图片

盗贼有多种选择可以走到这个位置,如果每次到这都进入递归,岂不是浪费时间?所以说存在重叠子问题,可以用备忘录进行优化

House Robber II

这道题目和第一道描述基本一样,强盗依然不能抢劫相邻的房子,输入依然是一个数组,但是告诉你这些房子不是一排,而是围成了一个圈

首先,首尾房间不能同时被抢,那么只可能有三种不同情况:要么都不被抢;要么第一间房子被抢最后一间不抢;要么最后一间房子被抢第一间不抢。

图片

那就简单了啊,这三种情况,哪种的结果最大,就是最终答案呗!不过,其实我们不需要比较三种情况,只要比较情况二和情况三就行了,****因为这两种情况对于房子的选择余地比情况一大呀,房子里的钱数都是非负数,所以选择余地大,最优决策结果肯定不会小

posted @ 2022-04-13 10:15  BailanZ  阅读(25)  评论(0编辑  收藏  举报