The thief has found himself a new place for his thievery again. There is only one entrance to this area, called root.
In this new place, there are houses and each house has its only one parent house. All houses in this place form a binary tree. It will automatically contact the police if two directly-linked houses were broken.
You are given the root of the binary tree, return the maximum amount of money the thief can rob without alerting the police.
Example 1:
Input: root = [1,4,null,2,3,3]
Output: 7Explanation: Maximum amount of money the thief can rob = 4 + 3 = 7
Example 2:
Input: root = [1,null,2,3,5,4,2]
Output: 12Explanation: Maximum amount of money the thief can rob = 1 + 4 + 2 + 5 = 12
Constraints:
1 <= The number of nodes in the tree <= 10,000.0 <= Node.val <= 10,000Before attempting this problem, you should be comfortable with:
This is a tree version of the classic house robber problem.
At each node, we have two choices: rob it or skip it.
If we rob the current node, we cannot rob its immediate children, so we must skip to the grandchildren.
If we skip the current node, we can consider robbing its children.
We take the maximum of these two options.
null, return 0.left.left, left.right, right.left, right.right).# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
res = root.val
if root.left:
res += self.rob(root.left.left) + self.rob(root.left.right)
if root.right:
res += self.rob(root.right.left) + self.rob(root.right.right)
res = max(res, self.rob(root.left) + self.rob(root.right))
return resThe recursive solution recomputes results for the same nodes multiple times.
By storing computed results in a cache (hash map), we avoid redundant work.
Each node is processed at most once, significantly improving efficiency.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: Optional[TreeNode]) -> int:
cache = { None : 0 }
def dfs(root):
if root in cache:
return cache[root]
cache[root] = root.val
if root.left:
cache[root] += dfs(root.left.left) + dfs(root.left.right)
if root.right:
cache[root] += dfs(root.right.left) + dfs(root.right.right)
cache[root] = max(cache[root], dfs(root.left) + dfs(root.right))
return cache[root]
return dfs(root)Instead of caching all nodes, we can return two values from each subtree: the maximum if we rob this node, and the maximum if we skip it.
This eliminates the need for a hash map.
For each node, "with root" equals the node value plus the "without" values of both children.
"Without root" equals the sum of the maximum values (either with or without) from both children.
[maxWithNode, maxWithoutNode].null node, return [0, 0].withRoot as the node's value plus leftPair[1] plus rightPair[1] (children must be skipped).withoutRoot as max(leftPair) plus max(rightPair) (children can be robbed or skipped).[withRoot, withoutRoot].# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def rob(self, root: TreeNode) -> int:
def dfs(root):
if not root:
return [0, 0]
leftPair = dfs(root.left)
rightPair = dfs(root.right)
withRoot = root.val + leftPair[1] + rightPair[1]
withoutRoot = max(leftPair) + max(rightPair)
return [withRoot, withoutRoot]
return max(dfs(root))When robbing the current node, you cannot rob its immediate children, so you recursively consider the grandchildren. However, a common mistake is thinking you must rob the grandchildren. In reality, for each grandchild, you still have the choice to rob it or skip it. The recursive call on grandchildren will make this decision optimally. The constraint only prevents robbing adjacent nodes (parent-child), not skipping multiple levels.
When calculating the value of robbing the current node, you need to add values from grandchildren. If a child is null, accessing child.left or child.right will cause a null pointer error. Always check if the left or right child exists before attempting to access their children. A null child contributes 0 to the total, and its non-existent children also contribute 0.
The optimal DFS solution returns a pair [withRoot, withoutRoot] representing the maximum money if we rob or skip the current node. At the root level, the final answer is the maximum of these two values, not just one of them. Forgetting to take this maximum and returning only withRoot or withoutRoot will give an incorrect result whenever the optimal strategy at the root differs from what you returned.