Leetcode337. House Robber III

Leetcode337. 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.

这题的解法可以看做动态规划的一步一步优化过程

解法一 暴力递归 最优子结构

最优子结构:通过子问题的最值得到原问题的最值

当前节点(爷爷)能偷的最大钱数 money:

  1. 爷爷节点偷:2个儿子节点不能偷,4个孙子节点能偷,即1爷爷+4孙子的钱
    int money_sob = root.val + rob(root.left.left)+ rob(root.left.right)+ rob(root.right.left)+ rob(root.right.right)

  2. 爷爷节点不偷:2个儿子节点偷,4个孙子节点不偷,即2儿子的钱
    int money_nosob = rob(root.left) + rob(root.right)

  3. 选二者中的大的:int money = Math.max(money_sob, money_nosob)

public int rob(TreeNode root) {
    if (root == null) return 0;    
    int val = root.val;    
    if (root.left != null) {
        val += rob(root.left.left) + rob(root.left.right);
    }    
    if (root.right != null) {
        val += rob(root.right.left) + rob(root.right.right);
    }    
    return Math.max(val, rob(root.left) + rob(root.right));
}

解法二 记忆化 解决重复子问题

动态规划的两个条件:1. 有最优子结构 2. 重复子问题

解法一的问题出现在:当在爷爷节点计算了2个儿子节点和四个孙子节点,而在下一个递归中到儿子节点我们又重复计算了孙子节点的值。因此我们可以将上一次递归的子孙节点的值存起来,下次递归就不需要重复计算了。所以这题应该用动态规划。

在大多数情况我们使用数组来存储上一次的值,但是数组不适合缓存二叉树,这里使用哈希表来存储节点信息,哈希表的key是Node,哈希表的value是能偷的钱。

  • 时间复杂度O(n)
  • 空间复杂度O(n)
HashMap<TreeNode, Integer> map = new HashMap<>();
public int rob(TreeNode root) {
    if (root == null) return 0;
    if (map.containsKey(root)) return map.get(root);    
    int val = root.val; 

    if (root.left != null) {
        val += rob(root.left.left) + rob(root.left.right);
    }    
    if (root.right != null) {
        val += rob(root.right.left) + rob(root.right.right);
    }    
    val = Math.max(val, rob(root.left) + rob(root.right));
    map.put(root, val);    
    return val;
}

解法三 树形动态规划

这个解法和Leetcode309. Best Time to Buy and Sell Stock with Cooldown的思路相似

每个节点都有两种状态:偷或者不偷。

定义int[] dp = new int[2]表示到当前节点结束后(在当前节点偷/不偷)我们今晚一共可以得到的最大钱数,其中dp[0]表示不偷的状态,dp[1]代表的状态。

  • 不偷:今晚到此节点时一共得到的钱数 = 今晚到左孩子后一共得到的最大钱数 + 今晚到右孩子后共得到的最大钱数(取孩子结点两个状态的最大值)。

    dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);

    不能写成dp[0] = left[0] + right[0];,这种写法默认左孩子偷,右孩子也偷。

  • :今晚到此节点时一共得到的钱数 = 当前节点的钱数 + 左孩子状态不偷 + 右孩子状态不偷
    dp[1] = root.val + left[0] + right[0]

① 当前节点不偷 = 左节点[max(偷/不偷)] + 右结点[max(偷/不偷)]
② 当前节点偷 = 当前节点钱 + 左节点[不偷] + 右节点[不偷]

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

private int[] robSub(TreeNode root) {
    if (root == null) return new int[2];
    
    int[] left = robSub(root.left);
    int[] right = robSub(root.right);
    int[] dp = new int[2];

    dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
    dp[1] = root.val + left[0] + right[0];
    
    return dp;
}

你可能感兴趣的:(leetcode)