House Robber III
Tree-shaped house arrangement — return max loot using DP returning (rob-root, skip-root) per node.
3 min read
backtracking tree dp
Problem#
Houses arranged in a binary tree. Adjacent (parent-child) houses can’t both be robbed. Return the maximum total loot.
Examples#
[3,2,3,null,3,null,1]→7
Constraints#
1 <= n <= 10^4.
Approach#
Post-order DFS returning a pair (rob, skip) per subtree. rob = val + leftSkip + rightSkip; skip = max(leftRob, leftSkip) + max(rightRob, rightSkip). Answer is max of the root’s pair.
Solution#
class Solution {public: int rob(TreeNode* root) { auto [a, b] = dfs(root); return max(a, b); }private: pair<int,int> dfs(TreeNode* n) { if (!n) return {0, 0}; auto [lRob, lSkip] = dfs(n->left); auto [rRob, rSkip] = dfs(n->right); int rob = n->val + lSkip + rSkip; int skip = max(lRob, lSkip) + max(rRob, rSkip); return {rob, skip}; }};func rob(root *TreeNode) int { maxI := func(a, b int) int { if a > b { return a } return b } var dfs func(n *TreeNode) (int, int) dfs = func(n *TreeNode) (int, int) { if n == nil { return 0, 0 } lRob, lSkip := dfs(n.Left) rRob, rSkip := dfs(n.Right) rob := n.Val + lSkip + rSkip skip := maxI(lRob, lSkip) + maxI(rRob, rSkip) return rob, skip } a, b := dfs(root) return maxI(a, b)}from typing import Optional, Tuple
class Solution: def rob(self, root: Optional[TreeNode]) -> int: def dfs(node: Optional[TreeNode]) -> Tuple[int, int]: if not node: return 0, 0 l_rob, l_skip = dfs(node.left) r_rob, r_skip = dfs(node.right) rob_here = node.val + l_skip + r_skip skip_here = max(l_rob, l_skip) + max(r_rob, r_skip) return rob_here, skip_here
return max(dfs(root))function rob(root) { function dfs(node) { if (!node) return [0, 0]; const [lRob, lSkip] = dfs(node.left); const [rRob, rSkip] = dfs(node.right); const robHere = node.val + lSkip + rSkip; const skipHere = Math.max(lRob, lSkip) + Math.max(rRob, rSkip); return [robHere, skipHere]; } const [a, b] = dfs(root); return Math.max(a, b);}class Solution { public int rob(TreeNode root) { int[] res = dfs(root); return Math.max(res[0], res[1]); }
private int[] dfs(TreeNode n) { if (n == null) return new int[]{0, 0}; int[] l = dfs(n.left); int[] r = dfs(n.right); int rob = n.val + l[1] + r[1]; int skip = Math.max(l[0], l[1]) + Math.max(r[0], r[1]); return new int[]{rob, skip}; }}function rob(root: TreeNode | null): number { const dfs = (node: TreeNode | null): [number, number] => { if (!node) return [0, 0]; const [lRob, lSkip] = dfs(node.left); const [rRob, rSkip] = dfs(node.right); const robHere = node.val + lSkip + rSkip; const skipHere = Math.max(lRob, lSkip) + Math.max(rRob, rSkip); return [robHere, skipHere]; }; const [a, b] = dfs(root); return Math.max(a, b);}Editorial#
Returning two values per call avoids recomputation. The DP recurrence is “either rob this node (forbid children) or skip it (children free to do either)” — same structure as the linear House Robber but on a tree.
Complexity#
- Time: O(n).
- Space: O(h) recursion.
Concept revision#
Related#