LeetCode 337. House Robber III

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

Example 1:

Input: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

Output: 7 
Explanation: Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.

Example 2:

Input: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1

Output: 9
Explanation: Maximum amount of money the thief can rob = 4 + 5 = 9.

首先明确题意:对于任何一个节点node,抢劫限制条件只作用在以node为根节点的子树上。

分为两种情况:

抢了root节点: root.left和root.right不能抢了。 root.left.left, root.left.right, root.right.left, root.right.right都可以抢

没有抢root节点: root.left和root.right都还可以抢

简单的递归如下:由于存在大量重叠的子结构,重复计算过多,很慢。需要500ms左右

    //naive implementation
    public int rob(TreeNode root) {
        if(root==null) return 0;
        
        //没抢root: 那么root.left, root.right都可以抢,且可以同时抢
        int res1 =  rob(root.left)+rob(root.right);
        
        //抢了root: 那么root.left和root.right都不能抢了。root.left.left, root.left.right, root.right.left, root.right.right都是可以抢的,且可以同时抢
        int res2 = root.val;
        if(root.left!=null){
            res2 += rob(root.left.left) + rob(root.left.right);
        }
        if(root.right!=null){
            res2 += rob(root.right.left) + rob(root.right.right);
        }
        return Math.max(res1,res2);
    }

既然有大量重复子结构,那么自然想到了动态规划这个记忆算法:

这里用hashmap来实现记忆功能

    //记忆
    public int rob(TreeNode root) {
        return robSub(root, new HashMap<>());
    }
    private int robSub(TreeNode root, Map<TreeNode, Integer> map) {
        if(root==null) return 0;
        if(map.containsKey(root)) return map.get(root);
        
        //没抢root: 那么root.left, root.right都可以抢,且可以同时抢
        int res1 =  robSub(root.left,map)+robSub(root.right,map);
        
        //抢了root: 那么root.left和root.right都不能抢了。root.left.left, root.left.right, root.right.left, root.right.right都是可以抢的,且可以同时抢
        int res2 = root.val;
        if(root.left!=null){
            res2 += robSub(root.left.left,map) + robSub(root.left.right,map);
        }
        if(root.right!=null){
            res2 += robSub(root.right.left,map) + robSub(root.right.right,map);
        }
        map.put(root,Math.max(res1,res2));//存储起来
        return Math.max(res1,res2);
    }

上面的算法只需要4ms

动态规划可以高效解决重复子结构问题,那么换一种思路:

有没有办法可以直接避免 重复子结构呢?

其实也很简单,让函数同时返回抢root和不抢root这两种情况下的最大值。此时的分治法不再有重叠子结构。

这样子,只需要0ms

    //final version 
    //robSub(root)分解为了robSub(root.left)和rootSub(root.right),两部分没有重叠。
    public int rob(TreeNode root) {
        int[] res = robSub(root);
        return Math.max(res[0],res[1]);
    }
    private int[] robSub(TreeNode root) {
        int[] res = new int[2];
        if(root==null) return res;
        int[] left = robSub(root.left);//左子树问题
        int[] right = robSub(root.right);//右子树问题、
        //由左右子树的结果得出根节点的结果
        res[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);//没抢root
        res[1] = root.val;//抢了root
        res[1] += left[0] + right[0];
        return res;
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值