🧠
Bottom-Up DP with Post-Order Traversal
💡 This approach uses a bottom-up traversal to compute the answer for each node based on its children, avoiding recursion overhead and making the logic clearer.
Intuition
For each node, compute two values: max money if robbed and max money if not robbed, using children's results.
Algorithm
- Traverse the tree in post-order (children before parent).
- For each node, compute two values: rob and not_rob.
- rob = node.val + sum of not_rob from children.
- not_rob = sum of max(rob, not_rob) from children.
- Return max of rob and not_rob at root.
💡 This approach cleanly separates the two states per node and uses children's results to build parent's answer.
Recurrence:dp[node].rob = node.val + sum(dp[child].not_rob); dp[node].not_rob = sum(max(dp[child].rob, dp[child].not_rob))
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def rob(root):
def dfs(node):
if not node:
return (0, 0) # (not_rob, rob)
left = dfs(node.left)
right = dfs(node.right)
rob_node = node.val + left[0] + right[0]
not_rob_node = max(left) + max(right)
return (not_rob_node, rob_node)
return max(dfs(root))
# Example usage:
# root = TreeNode(3, TreeNode(2, None, TreeNode(3)), TreeNode(3, None, TreeNode(1)))
# print(rob(root)) # Output: 7
Line Notes
def dfs(node):Helper function to return tuple (not_rob, rob) for node
if not node:Base case returns zero for both states
rob_node = node.val + left[0] + right[0]Rob current node plus not robbing children
not_rob_node = max(left) + max(right)Don't rob current node, take max of rob or not rob children
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
}
public class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
private int[] dfs(TreeNode node) {
if (node == null) return new int[]{0, 0};
int[] left = dfs(node.left);
int[] right = dfs(node.right);
int rob = node.val + left[0] + right[0];
int notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return new int[]{notRob, rob};
}
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
root.left = new TreeNode(2);
root.left.right = new TreeNode(3);
root.right = new TreeNode(3);
root.right.right = new TreeNode(1);
Solution sol = new Solution();
System.out.println(sol.rob(root)); // Output: 7
}
}
Line Notes
private int[] dfs(TreeNode node)Returns array with two states: notRob and rob
if (node == null) return new int[]{0, 0};Base case returns zero for both states
int rob = node.val + left[0] + right[0];Rob current node plus not robbing children
int notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);Don't rob current node, take max of rob or not rob children
#include <iostream>
#include <algorithm>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
pair<int,int> dfs(TreeNode* node) {
if (!node) return {0,0};
auto left = dfs(node->left);
auto right = dfs(node->right);
int rob = node->val + left.first + right.first;
int notRob = max(left.first, left.second) + max(right.first, right.second);
return {notRob, rob};
}
int rob(TreeNode* root) {
auto res = dfs(root);
return max(res.first, res.second);
}
};
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(2);
root->left->right = new TreeNode(3);
root->right = new TreeNode(3);
root->right->right = new TreeNode(1);
Solution sol;
cout << sol.rob(root) << endl; // Output: 7
return 0;
}
Line Notes
pair<int,int> dfs(TreeNode* node)Returns pair (notRob, rob) for node
if (!node) return {0,0};Base case returns zero for both states
int rob = node->val + left.first + right.first;Rob current node plus not robbing children
int notRob = max(left.first, left.second) + max(right.first, right.second);Don't rob current node, take max of rob or not rob children
function TreeNode(val, left=null, right=null) {
this.val = val;
this.left = left;
this.right = right;
}
function rob(root) {
function dfs(node) {
if (!node) return [0, 0];
const left = dfs(node.left);
const right = dfs(node.right);
const rob = node.val + left[0] + right[0];
const notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return [notRob, rob];
}
const res = dfs(root);
return Math.max(res[0], res[1]);
}
// Example usage:
// const root = new TreeNode(3, new TreeNode(2, null, new TreeNode(3)), new TreeNode(3, null, new TreeNode(1)));
// console.log(rob(root)); // Output: 7
Line Notes
function dfs(node)Returns array [notRob, rob] for node
if (!node) return [0, 0];Base case returns zero for both states
const rob = node.val + left[0] + right[0];Rob current node plus not robbing children
const notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);Don't rob current node, take max of rob or not rob children
TimeO(n)
SpaceO(n) due to recursion stack
Each node is visited once in post-order traversal.
💡 For n=10^5, this approach is efficient and scalable.
Interview Verdict: Accepted
This is the optimal and cleanest approach for this problem.