337.House Robber III
Tags: [tree], [binary_tree], [recursion], [dp], [trick]
Link: https://leetcode.com/problems/house-robber-iii/\#/description
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:
3
/ \
2 3
\ \
3 1
Maximum amount of money the thief can rob = 3+3+1=7.
Example 2:
3
/ \
4 5
/ \ \
1 3 1
Maximum amount of money the thief can rob = 4+5=9.
Solution: recursion, trick
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
money_from_not_rob_root, money_from_rob_root = self.rob_helper(root)
return max(money_from_not_rob_root, money_from_rob_root)
def rob_helper(self, root):
# base case
if not root:
return (0, 0)
left_sub_money_from_not_rob_root, left_sub_money_from_rob_root = self.rob_helper(root.left)
right_sub_money_from_not_rob_root, right_sub_money_from_rob_root = self.rob_helper(root.right)
money_from_not_rob_root = max(left_sub_money_from_not_rob_root, left_sub_money_from_rob_root) +\
max(right_sub_money_from_not_rob_root, right_sub_money_from_rob_root)
money_from_rob_root = root.val + left_sub_money_from_not_rob_root + right_sub_money_from_not_rob_root
return (money_from_not_rob_root, money_from_rob_root)
Revelation:
- If the thief robs the root, he cannot rob root.left and root.right.
- If the thief doesn't rob the root, he can rob any node in the root's left subtree and root's right subtree.
- More explanation about why the above idea comes out can see this reference: https://leetcode.com/problems/house-robber-iii/\#/solutions
Note:
- Time complexity = O(n), n is the number of nodes of this tree. Each node we just visited once.
Time Limited Exceeded
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
memo = {}
return self.rob_helper(root, memo)
def rob_helper(self, root, memo):
if not root:
return 0
if root in memo:
return memo[root]
# not rob root
money_if_not_rob_root = self.rob(root.left) + self.rob(root.right)
# rob root
money_if_rob_root = root.val
if root.left:
money_if_rob_root += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
money_if_rob_root += self.rob(root.right.left) + self.rob(root.right.right)
max_money = max(money_if_not_rob_root, money_if_rob_root)
memo[root] = max_money
return max_money
Note:
- In Python, the custom object can be used as the key of map and set, because the default __hash_ function will return id(x) / 16, id() is a Python build_in function which will return the object's id. For example, there is a custom class called Hello, we can initialize an object, hello = Hello(), id(hello) will return the id of object "hello".
- Time complexity = O(n), n is the number of nodes of the given tree. Personally I think the reason this algorithm is slower than the first solution above is because there are many extra calls self.rob_helper(...) in this algorithm. Even though if the current root has been put into the memo, the self.rob_helper(...) can immediately return the memo[root], but it still wast some time during calling the self.rob_helper(...).
Time Limited Exceeded
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
# base case
if not root:
return 0
# not rob root
money_if_not_rob_root = self.rob(root.left) + self.rob(root.right)
# rob root
money_if_rob_root = root.val
if root.left:
money_if_rob_root += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
money_if_rob_root += self.rob(root.right.left) + self.rob(root.right.right)
return max(money_if_not_rob_root, money_if_rob_root)