@@ -26,3 +26,158 @@ Total amount you can rob = 2 + 9 + 1 = 12.
2626
2727- Array
2828- Dynamic Programming
29+
30+ ---
31+ # House Robber III
32+
33+ The thief has found himself a new place for his thievery again. There is only one entrance to this area, called root.
34+
35+ Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that all houses
36+ in this place form a binary tree. It will automatically contact the police if two directly-linked houses were broken
37+ into on the same night.
38+
39+ Given the root of the binary tree, return the maximum amount of money the thief can rob without alerting the police.
40+
41+ ## Constraints
42+
43+ - The number of nodes in the tree is in the range [ 1, 10^4] .
44+ - 0 <= Node.val <= 10^4
45+
46+ ## Examples
47+
48+ ![ Example 1] ( ./images/examples/house_robber_3_example_1.png )
49+ ![ Example 1.1] ( ./images/examples/house_robber_3_example_1.1.png )
50+ ![ Example 1.2] ( ./images/examples/house_robber_3_example_1.2.png )
51+ ![ Example 1.3] ( ./images/examples/house_robber_3_example_1.3.png )
52+ ![ Example 1.4] ( ./images/examples/house_robber_3_example_1.4.png )
53+ ![ Example 2] ( ./images/examples/house_robber_3_example_2.png )
54+
55+ ![ Example 3] ( ./images/examples/house_robber_3_example_3.png )
56+ ``` text
57+ Input: root = [3,2,3,null,3,null,1]
58+ Output: 7
59+ Explanation: Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
60+ ```
61+
62+ ![ Example 4] ( ./images/examples/house_robber_3_example_4.png )
63+ ``` text
64+ Input: root = [3,4,5,1,3,null,1]
65+ Output: 9
66+ Explanation: Maximum amount of money the thief can rob = 4 + 5 = 9.
67+ ```
68+
69+ ## Topics
70+
71+ - Dynamic Programming
72+ - Tree
73+ - Depth-First Search
74+ - Binary Tree
75+
76+ ## Solution(s)
77+
78+ 1 . [ Recursion] ( #recursion )
79+ 2 . [ Dynamic Programming(Memoization) - Top Down Approach] ( #dynamic-programmingmemoization-top-down-approach )
80+ 3 . [ Dynamic Programming(Optimal)] ( #dynamic-programming-optimal--bottom-up-approach )
81+
82+ ### Recursion
83+
84+ 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
85+ rob the current node, we cannot rob its immediate children, so we must skip to the grandchildren. If we skip the current
86+ node, we can consider robbing its children. We take the maximum of these two options.
87+
88+ #### Algorithm
89+
90+ - If the node is null, return 0.
91+ - Calculate the value if we rob the current node: add the node's value plus the result from its grandchildren
92+ (left.left, left.right, right.left, right.right).
93+ - Calculate the value if we skip the current node: add the results from robbing the left and right children.
94+ - Return the maximum of these two values.
95+
96+ #### Time Complexity
97+
98+ O(2^n)
99+
100+ #### Space complexity
101+
102+ O(n) for recursion stack.
103+
104+ ### Dynamic Programming(Memoization) Top-Down Approach
105+
106+ The recursive solution recomputes results for the same nodes multiple times. By storing computed results in a cache
107+ (hash map), we avoid redundant work. Each node is processed at most once, significantly improving efficiency.
108+
109+ #### Algorithm
110+
111+ - Create a cache (hash map) to store computed results for each node.
112+ - Define a recursive function that checks the cache before computing.
113+ - If the node is in the cache, return the cached result.
114+ - Otherwise, compute the result using the same logic as the basic recursion: max of robbing current node
115+ (plus grandchildren) vs skipping current node (plus children).
116+ - Store the result in the cache and return it.
117+
118+ #### Complexity Analysis
119+
120+ - Time complexity: O(n)
121+ - Space complexity: O(n)
122+
123+ ### Dynamic Programming (Optimal)- Bottom-Up Approach
124+
125+ Instead of caching all nodes, we can return two values from each subtree: the maximum if we rob this node, and the
126+ maximum if we skip it. This eliminates the need for a hash map. For each node, "with root" equals the node value plus
127+ the "without" values of both children. "Without root" equals the sum of the maximum values (either with or without) from
128+ both children.
129+
130+ #### Algorithm
131+
132+ - Define a recursive function that returns a pair: [ maxWithNode, maxWithoutNode] .
133+ - For a null node, return [ 0, 0] .
134+ - Recursively get the pairs for left and right children.
135+ - Calculate withRoot as the node's value plus leftPair[ 1] plus rightPair[ 1] (children must be skipped).
136+ - Calculate withoutRoot as max(leftPair) plus max(rightPair) (children can be robbed or skipped).
137+ - Return [ include_root, exclude_root] .
138+ - The final answer is the maximum of the two values returned for the root.
139+
140+ ![ Solution 1] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_1.png )
141+ ![ Solution 2] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_2.png )
142+ ![ Solution 3] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_3.png )
143+ ![ Solution 4] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_4.png )
144+ ![ Solution 5] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_5.png )
145+ ![ Solution 6] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_6.png )
146+ ![ Solution 7] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_7.png )
147+ ![ Solution 8] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_8.png )
148+ ![ Solution 9] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_9.png )
149+ ![ Solution 10] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_10.png )
150+ ![ Solution 11] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_11.png )
151+ ![ Solution 12] ( ./images/solutions/house_robber_iii_solution_dp_bottom_up_12.png )
152+
153+ #### Complexity Analysis O(n)
154+
155+ The time complexity of this solution is O(n), where n is the number of nodes in the tree, since we visit all nodes once.
156+
157+ ##### Space complexity: O(n)
158+
159+ The space complexity of this solution is O(n), since the maximum depth of the recursive call tree is the height of the
160+ tree. Which is n in the worst case, and each call stores its data on the stack.
161+
162+ ### Common Pitfalls
163+
164+ #### Confusing "Skip to Grandchildren" with "Must Rob Grandchildren"
165+
166+ When robbing the current node, you cannot rob its immediate children, so you recursively consider the grandchildren.
167+ However, a common mistake is thinking you must rob the grandchildren. In reality, for each grandchild, you still have
168+ the choice to rob it or skip it. The recursive call on grandchildren will make this decision optimally. The constraint
169+ only prevents robbing adjacent nodes (parent-child), not skipping multiple levels.
170+
171+ #### Not Handling Null Children Properly
172+
173+ When calculating the value of robbing the current node, you need to add values from grandchildren. If a child is null,
174+ accessing child.left or child.right will cause a null pointer error. Always check if the left or right child exists
175+ before attempting to access their children. A null child contributes 0 to the total, and its non-existent children also
176+ contribute 0.
177+
178+ #### Forgetting to Take Maximum in the Final Answer
179+
180+ The optimal DFS solution returns a pair [ withRoot, withoutRoot] representing the maximum money if we rob or skip the
181+ current node. At the root level, the final answer is the maximum of these two values, not just one of them. Forgetting
182+ to take this maximum and returning only withRoot or withoutRoot will give an incorrect result whenever the optimal
183+ strategy at the root differs from what you returned.
0 commit comments