一、题目描述

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。

这个地区只有一个入口,我们称之为根

除了“根”之外,每栋房子有且只有一个“父“房子与之相连。

一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

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

示例 2:

输入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

二、题目解析

三、参考代码

1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 打家劫舍III( LeetCode 337 ):https://leetcode-cn.com/problems/house-robber-iii/submissions/
class Solution {

    public int rob(TreeNode root) {

        // 每个节点都有两种选择:偷、不偷
        // 从叶子节点开始判断,直到来到根节点
        int[] res = chooseNode(root);

        // 选择当前节点偷和不偷抉择时的较大值
        return Math.max(res[0], res[1]);
    }

    private int[] chooseNode(TreeNode node) {
        // 递归终止条件
        // 即在叶子节点的时候,叶子节点的左右子节点为 null
        // 继续调用下去就会返回给叶子节点,它的左右子树的选择的金额都是 0
        if (node == null) {
            return new int[]{0, 0};
        }

        // 否则的话,说明当前节点有值
        // 需要判断,当前节点是偷还是不偷

        // 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        // 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        // 3、如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和

        // dp[0] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【不偷】 node 这个节点的策略
        // dp[1] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【偷】 node 这个节点的策略
        int[] dp = new int[2];

        // 先去计算左子节点的选择
        int[] left = chooseNode(node.left);

        // 再去计算右子节点的选择
        int[] right = chooseNode(node.right);


        // 如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和
        dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);

        // 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        // 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        // 3、再加上当前节点的金额
        dp[1] = left[0] + right[0] + node.val;

        return dp;
    }
}

2、C++ 代码

class Solution {
public:
    int rob(TreeNode* root) {
        // 每个节点都有两种选择:偷、不偷
        // 从叶子节点开始判断,直到来到根节点
        vector<int> res = chooseNode(root);

        // 选择当前节点偷和不偷抉择时的较大值
        return max(res[0], res[1]);
    }

private:
    vector<int> chooseNode(TreeNode* node) {

        // 递归终止条件
        // 即在叶子节点的时候,叶子节点的左右子节点为 null
        // 继续调用下去就会返回给叶子节点,它的左右子树的选择的金额都是 0
        if (node == NULL) {
            return vector<int>{0, 0};
        }

        // 否则的话,说明当前节点有值
        // 需要判断,当前节点是偷还是不偷

        // 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        // 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        // 3、如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和

        // dp[0] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【不偷】 node 这个节点的策略
        // dp[1] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【偷】 node 这个节点的策略
        vector<int> dp(2);

        // 先去计算左子节点的选择
        vector<int> left = chooseNode(node->left);

        // 再去计算右子节点的选择
        vector<int> right = chooseNode(node->right);


        // 如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和
        dp[0] = max(left[0], left[1]) + max(right[0], right[1]);

        // 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        // 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        // 3、再加上当前节点的金额
        dp[1] = left[0] + right[0] + node->val;

        return dp;
    }

};

3、Python 代码

class Solution:
    def rob(self, root: TreeNode) -> int:
        # 每个节点都有两种选择:偷、不偷
        # 从叶子节点开始判断,直到来到根节点
        res = self.chooseNode(root)

        # 选择当前节点偷和不偷抉择时的较大值
        return max(res[0], res[1])


    def chooseNode(self, node):
        # 递归终止条件
        # 即在叶子节点的时候,叶子节点的左右子节点为 null
        # 继续调用下去就会返回给叶子节点,它的左右子树的选择的金额都是 0
        if node is None:
            return (0, 0) 

        # 否则的话,说明当前节点有值
        # 需要判断,当前节点是偷还是不偷

        # 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        # 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        # 3、如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和

        # dp[0] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【不偷】 node 这个节点的策略
        # dp[1] 表示的是以当前 node 为根节点的子树能够偷取的最大金额,并且此时采取【偷】 node 这个节点的策略
        dp = [ 0 ,0 ]

        # 先去计算左子节点的选择
        left = self.chooseNode(node.left)

        # 再去计算右子节点的选择
        right = self.chooseNode(node.right)


        # 如果选择不偷,那么可以偷左右子节点,这个时候的最大金额为左右子节点最大金额之和
        dp[0] = max(left[0], left[1]) + max(right[0], right[1]) 

        # 1、如果选择偷,那么左子节点不能偷,这个时候需要获取不偷左子节点时的最大金额
        # 2、如果选择偷,那么右子节点不能偷,这个时候需要获取不偷右子节点时的最大金额
        # 3、再加上当前节点的金额
        dp[1] = left[0] + right[0] + node.val 
        return dp 

四、动画理解(没有声音)