LeetCode 337.打家劫舍 III

146 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目:这个小偷偷的地方可有意思了,这个地方只有一个入口,并且里面的房子呈树形相连,每个房子有且只要一个父房子和其相连,此时要求小偷在此区域偷窃,并且不能同时偷两个相连的房子。求小偷可偷盗的最大金额。

解题思路

本题为典型的树形DP的问题,假设此时的树为三层,小偷不能偷相连房子,那么此时小偷可偷的选择就为:

  • 偷爷爷结点和全部孙子结点
  • 偷所有儿子结点

上述两个金额较大的即为答案,由此可得代码:

public int rob(TreeNode root) {
    if(root == null) return 0;
    int money = root.val;

    if(root.left!=null){
        money += rob(root.left.left) + rob(root.left.right);
    }

    if(root.right!=null){
        money += rob(root.right.left) + rob(root.right.right);
    }

    return Math.max(money, rob(root.left) + rob(root.right));
}

方法较为暴力,喜提超时。那么问题出在哪里呢?原来爷爷在计算孙子的钱的时候也计算了儿子的钱,这样层层递归就会多计算很多步,由此我们可以用空间换时间,将每次的结果保存下来,这样时间复杂度自然下来了,可得代码如下:

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

public int rob(TreeNode root, HashMap<TreeNode, Integer> map) {
    if(root == null) return 0;

    if(map.containsKey(root)) return map.get(root);

    int money = root.val;

    if(root.left!=null){
        money += rob(root.left.left, map) + rob(root.left.right, map);
    }

    if(root.right!=null){
        money += rob(root.right.left, map) + rob(root.right.right, map);
    }

    int result = Math.max(money, rob(root.left, map) + rob(root.right, map));
    map.put(root, result);

    return result;
}

上述代码最终耗时2ms,超越的人也不多。

在看题解的时候我发现这样一个问题,有人说上面假设的是三层树,结果只有爷爷和孙子的金额和儿子的相比,那么如果此时树是四层,答案也可能是爷爷和重孙的比较大,上面的思路就没有考虑这一点。

**解答:**实际上递归是自底向上进行的,如果此时树存在四层,那么假设我们当前处于第三层(第四层可偷显然是自身),那么由第三层结点作为根节点就只有两种情况:偷自己还是偷儿子!以最终结果来代替作为新的第三层,如果是偷自己,那结果就还是爷爷加孙子,但如果是偷儿子,那结果就是爷爷加重孙!

进阶解法

实际上对于一个结点来说无非只有两种情况:偷或者不偷,那么我们可得:

  • 偷:此时的最大金额 = 自身的钱 + 左儿子不偷能偷到的钱 + 右儿子不偷能偷到的钱
  • 不偷:此时的最大金额 = 左儿子能偷盗的钱(可偷可不偷自身) + 右儿子能偷盗的钱(可偷可不偷自身)

此时我们可以使用大小为2的数组来表示偷和不偷,代码如下:

public int rob(TreeNode root) {
    int[] result = rab(root);
    return Math.max(result[0], result[1]);
}

public int[] rab(TreeNode root){
    if(root==null) return new int[2];
    int[] result = new int[2];

    int[] left = rab(root.left);
    int[] right = rab(root.right);

    result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); //不偷
    result[1] = left[0] + right[0] + root.val; // 偷
    return result;
}

最终耗时0ms,牛逼牛逼牛逼!!!