【详细整理】337 打家劫舍 III

leetcode-cn.com/problems/ho…

题目

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

思路

最值:穷举 动态规划

base case:null 0;

状态:偷盗的金额,也就是不直接相邻的节点和

选择:它不相邻的节点

bp函数:需要实现最大金额的计算;

bp函数分析:来到一个节点,两种情况:1)选择偷它,然后选择偷它的孙子节点 2)不选择偷它然后偷选择它的子节点,比较二者值的大小,返回偷到的金额

可以用备忘录记录偷过的节点

代码

    public int rob(TreeNode root) {
        HashMap<TreeNode,Integer> map = new HashMap<>(); 
        return bp(root,map);

    }
    public int bp(TreeNode root,HashMap<TreeNode,Integer> map){
        if (root==null){
            return 0;
        }
        if (map.containsKey(root)){
            return map.get(root);
        }
        int l = bp(root.left,map);
        int r = bp(root.right,map);
        int a = l+r;
        int b = root.val;
        if (root.left != null){
            b += bp(root.left.left,map)+bp(root.left.right,map);
        }
        if (root.right != null){
            b += bp(root.right.left,map)+bp(root.right.right,map);
        }
        map.put(root,Math.max(a,b));
        return Math.max(a,b);
    }
}
复制代码

image.png

debug

    public int rob(TreeNode root) {
        // 用map当备忘录记录偷过的节点
        HashMap<TreeNode,Integer> map = new HashMap<>(); 
        return bp(root,map);

    }
    public int bp(TreeNode root,HashMap<TreeNode,Integer> map){
        if (root==null){
            return 0;
        }
        if (map.containsKey(root)){
            return map.get(root);
        }
        int l = bp(root.left,map);
        int r = bp(root.right,map);
        // 不偷这个点
        int a = l+r;
        // 偷这个节点
        int b = root.val;
        if (root.left != null){
            b += bp(root.left.left,map)+bp(root.left.right,map);
        }
        if (root.right != null){
            b += bp(root.right.left,map)+bp(root.right.right,map);
        }
        // 放进map里
        map.put(root,Math.max(a,b));
        // 返回大值
        return Math.max(a,b);
    }
}
复制代码

image.png
如果要调用root.left.right需要对root.left进行null 判断

代码优化

每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷

当前节点选择偷时,那么两个孩子节点就不能选择偷了
当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为

当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数

(观察上面发现,左右子树的两个状态影响当前子树的两个状态,但别的子树的状态影响不了。
因此没必要用 map 记录每一个子树的状态,递归总是子调用的解返回给父调用,所以只需在每次递归中用两个变量,存当前子问题的两个状态,返回出来给父调用即可)

第 1 步:状态定义
dp[node][j] :这里 node 表示一个结点,以 node 为根结点的树,并且规定了 node 是否偷取能够获得的最大价值。

j = 0 表示 node 结点不偷取;
j = 1 表示 node 结点偷取。
第 2 步: 推导状态转移方程
根据当前结点偷或者不偷,就决定了需要从哪些子结点里的对应的状态转移过来。

如果当前结点不偷,左右子结点偷或者不偷都行,选最大者;
如果当前结点偷,左右子结点均不能偷。
(状态转移方程的表述有点复杂,请大家直接看代码。)

第 3 步: 初始化
一个结点都没有,空节点,返回 0,对应后序遍历时候的递归终止条件;

第 4 步: 输出
在根结点的时候,返回两个状态的较大者。

第 5 步: 思考优化空间
优化不了。

(作者:liweiwei1419
链接:leetcode-cn.com/problems/ho…

    public int rob(TreeNode root) {
        int[] result = bp(root);
        return Math.max(result[0],result[1]);
    }
    public int[] bp(TreeNode root){
        if(root==null){
            return new int[2];
            // return new int[]{0, 0} 这种也可以,表示放00进去
        }
        int[] res =new int[2];
        int[] left =bp(root.left);
        int[] right = bp(root.right);
        res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
        res[1] = root.val + left[0] +right[0];
        return res;
    }
}

复制代码

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享