House Robber III

Updated on 02 June, 2025
House Robber III header image

Problem Statement

In this scenario, a thief targets a unique area structured in the form of a binary tree, where each house represents a node of the tree. The area's entrance is denoted by the root. Every house (node) is linked under a parent house following the binary tree structure, except for the root that serves as the primary entry point. Key to ensuring a successful robbery involves a strategic challenge; the thief cannot rob two directly associated houses (parent and child nodes) consecutively since this would activate an alarm system, alerting the police. The objective is to determine the maximum amount of money the thief can steal without triggering the police alert by robbing an inappropriate sequence of houses.

Examples

Example 1

Input:

root = [3,2,3,null,3,null,1]

Output:

7

Explanation:

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

Example 2

Input:

root = [3,4,5,1,3,null,1]

Output:

9

Explanation:

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

Constraints

  • The number of nodes in the tree is in the range [1, 104].
  • 0 <= Node.val <= 104

Approach and Intuition

The problem can be effectively approached by utilizing a dynamic programming technique on trees often referred to as "tree dp". The main intuition behind the solution is to make a decision at each node (house), whether to rob it or not, and this decision impacts the optimal strategy for its child nodes. To figure out the best strategy, consider the following:

  1. If a node is robbed, then its direct children cannot be robbed.
  2. If a node is not robbed, then the maximum money from this node is the sum of the maximum amounts that can be robbed from its children.

For implementation:

  1. A recursive function can be used where each call returns two values:
    • The maximum money that can be robbed if the current node is robbed.
    • The maximum money that can be robbed if the current node is not robbed.
  2. For a node node, if it is robbed, then its children cannot be robbed. Therefore, the value if robbed is node.val + notRob(left) + notRob(right).
  3. If the node is not robbed, then the maximum money from this node would be the maximum money robbed from either robbing or not robbing each child. So, it would be max(rob(left), notRob(left)) + max(rob(right), notRob(right)).

This recursive strategy is applied from the root down to the leaves of the binary tree, and since each node is processed once, the complexity remains efficient within the provided constraints. The approach ensures that we never make the mistake of robbing two directly linked houses by recursively considering each possible scenario.

Solutions

  • Java
  • Python
java
class Solution {
    public int rob(TreeNode root) {
        if (root == null) {
            return 0;
        }
        ArrayList<Integer> values = new ArrayList<>();
        HashMap<Integer, ArrayList<Integer>> nodeChildren = new HashMap<>();
        nodeChildren.put(-1, new ArrayList<>());
        int idx = -1;
        Queue<TreeNode> nodeQueue = new LinkedList<>();
        nodeQueue.add(root);
        Queue<Integer> indexQueue = new LinkedList<>();
        indexQueue.add(idx);

        while (!nodeQueue.isEmpty()) {
            TreeNode curr = nodeQueue.poll();
            int parentIdx = indexQueue.poll();
            if (curr != null) {
                idx++;
                values.add(curr.val);
                nodeChildren.put(idx, new ArrayList<>());
                nodeChildren.get(parentIdx).add(idx);

                nodeQueue.add(curr.left);
                indexQueue.add(idx);
                nodeQueue.add(curr.right);
                indexQueue.add(idx);
            }
        }

        int[] robCurrent = new int[idx + 1];
        int[] noRobCurrent = new int[idx + 1];

        for (int i = idx; i >= 0; i--) {
            ArrayList<Integer> children = nodeChildren.get(i);
            if (children.isEmpty()) {
                robCurrent[i] = values.get(i);
                noRobCurrent[i] = 0;
            } else {
                robCurrent[i] = values.get(i);
                for (int child : children) {
                    robCurrent[i] += noRobCurrent[child];
                    noRobCurrent[i] += Math.max(robCurrent[child], noRobCurrent[child]);
                }
            }
        }

        return Math.max(robCurrent[0], noRobCurrent[0]);
    }
}

The given Java solution effectively tackles the problem of maximizing the amount of money robbers can steal from a house, represented as a binary tree structure where values at each node represent the potential loot at each house. Here's a concise summary of how the solution operates:

  • First, the solution method checks if the tree is empty. If it is, it returns zero.
  • Utilizes two main data structures: ArrayList<Integer> to store the values of each node, and HashMap<Integer, ArrayList<Integer>> for tracking the indices of node children.
  • Leverages breadth-first traversal with the help of queues to create an indexed representation of the binary tree. This is done by storing values by level and noting parent-child relationships.
  • With the tree represented in indexed form, two arrays (robCurrent and noRobCurrent) are created to handle dynamic programming decisions. The first array stores the maximum possible loot when the current node is robbed, and the second when it is not.
  • Fills these dynamic programming arrays in a bottom-up manner. It ensures that on robbing a node, the children nodes cannot be robbed, and the maximum loot includes the greater value of robbing or not robbing the node’s children.
  • Finally, the solution returns the maximum of the values in robCurrent and noRobCurrent for the root node, determining the optimal strategy to maximize the loot.

This approach cleverly combines tree traversal with dynamic programming principles, ensuring an efficient and effective solution to the problem.

python
class Solution:
    def steal(self, root: TreeNode) -> int:
        if not root:
            return 0

        # Convert tree to list representation
        vals_list = []
        tree_dict = {-1: []}
        node_index = -1
        nodes_queue = [(root, -1)]
        while nodes_queue:
            current_node, parent_idx = nodes_queue.pop(0)
            if current_node:
                node_index += 1
                vals_list.append(current_node.val)
                tree_dict[node_index] = []
                tree_dict[parent_idx].append(node_index)
                nodes_queue.append((current_node.left, node_index))
                nodes_queue.append((current_node.right, node_index))

        # maximum profit when robbing the current node
        max_rob = [0] * (node_index + 1)

        # maximum profit without robbing the current node
        max_not_rob = [0] * (node_index + 1)

        for idx in reversed(range(node_index + 1)):
            if not tree_dict[idx]:  # if it's a leaf
                max_rob[idx] = vals_list[idx]
                max_not_rob[idx] = 0
            else:
                max_rob[idx] = vals_list[idx] + sum(max_not_rob[child] for child in tree_dict[idx])
                max_not_rob[idx] = sum(max(max_rob[child], max_not_rob[child]) for child in tree_dict[idx])

        return max(max_rob[0], max_not_rob[0])

The "House Robber III" problem involves maximizing the amount of money you can rob from a binary tree, where adjacent nodes (direct parent-child relationships) cannot both be robbed. The Python solution provided uses dynamic programming to address this challenge.

  • Implement the steal method, which operates on a binary tree structure defined by TreeNode. The method returns an integer representing the maximum amount you can steal without triggering adjacent alarms.

  • Begin by checking if the root is None; if true, return 0, indicating there's nothing to rob.

  • Convert the binary tree into a list and dictionary representation for easier access and manipulation during dynamic programming calculations. Use a breadth-first search (BFS) to populate vals_list with node values and tree_dict with parent-child relationships.

  • Establish two arrays, max_rob and max_not_rob. The max_rob array holds the maximum profit possible when robbing the corresponding node, and the max_not_rob array holds the maximum profit when not robbing the node.

  • Iterate from the bottom of the tree (leaf nodes) up to the root. For leaf nodes, directly assign the node's value to max_rob and set max_not_rob to 0. For non-leaf nodes, calculate the max_rob by adding the node's value to the sum of max_not_rob values for its children. Calculate max_not_rob as the sum of the maximum values between max_rob and max_not_rob of the children nodes.

  • Ultimately, return the maximum value between max_rob[0] and max_not_rob[0], which represents the best strategy's profit when starting from the root of the tree.

This method ensures efficiency by reducing the complex tree structure to lists and optimally calculating the potential profit using dynamic programming, thereby ensuring that the solution scales well with larger trees.

Comments

No comments yet.