Binary Trees · 29 of 35
Maximum Sum of nodes in Binary tree such that no two are adjacent
Pick nodes from a tree to make the biggest total, but never pick a parent and its child together, by having every node report two numbers: with me and without me.
- Simple way
- time O(2^n)
room O(h) - Clever way
- time O(n)
room O(h)
The problem, simply
Every node of a binary tree holds a number. Choose some nodes so that their total is as big as possible. The rule: you may never choose two nodes that are joined by a line, so never a parent together with its own child. Give back the biggest total.
This is the famous "house robber" puzzle on a tree: houses joined by a line share an alarm, so you cannot rob both.
A story to remember it
Picture it
The best choice is 9 and 8, for 17. Taking the root 1 would forbid 9 and 2, and 1 + 3 + 4 + 8 is only 16.
- answer
- ignored
Examples
Mixing floors wins
- In
[1, 9, 2, 3, 4, null, 8]- Out
17
Pick 9 (floor 1) and 8 (floor 2). They are not joined, because 8 is under 2, not under 9.
Root and grandchildren
- In
[1, 2, 3, 1, null, 4, 5]- Out
11
1 + 1 + 4 + 5 = 11, and 2 + 4 + 5 = 11 too. Both are best.
Skip the root
- In
[3, 4, 5, 1, 3, null, 1]- Out
9
4 + 5 = 9 beats 3 + 1 + 3 + 1 = 8.
One node or none
- In
[5] and []- Out
5 and 0
One node: take it. Empty tree: the total is 0.
Way 1: the simple way
At each node, try both choices with plain recursion.
- Take the node: add its value, skip its children, and solve each grandchild.
- Skip the node: solve each child.
Return the bigger one.
The helpers for 3, 4 and 8 are each solved twice: once through 'take 1' and once through 'skip 1'. In a deep tree this repeat grows very fast.
- checking
class Solution {
public int maxSum(TreeNode node) {
if (node == null) return 0;
int take = node.val; // take me, skip my children
if (node.left != null) take += maxSum(node.left.left) + maxSum(node.left.right);
if (node.right != null) take += maxSum(node.right.left) + maxSum(node.right.right);
int skip = maxSum(node.left) + maxSum(node.right); // skip me
return Math.max(take, skip);
}
}How long it takes: the same subtree is solved again from many places. On a long thin tree the number of calls grows like the Fibonacci numbers, which is exponential, so think of it as O(2^n). Extra room: the call stack, O(h). You can save it with a memo map from node to answer, which makes it O(n) time and O(n) room.
Way 2: the clever way
Make every node return two numbers at once, like the party notes:
take: the best total of my family if I am picked. Then my children are not picked, sotake = my value + left.skip + right.skip.skip: the best total if I am not picked. Each child may do what is best for it, soskip = max(left.take, left.skip) + max(right.take, right.skip).
An empty spot returns (0, 0). The answer is the bigger of the root's two numbers. Every node is solved exactly once, children before parents.
Each node reports take and skip
01 / 10
Start at the root. Nobody can decide before hearing from their children, so go down first.
- looking now
- done
- answer
- ignored
class Solution {
public int maxSum(TreeNode root) {
int[] best = solve(root);
return Math.max(best[0], best[1]);
}
// returns {take, skip} for this family
private int[] solve(TreeNode node) {
if (node == null) return new int[]{0, 0};
int[] left = solve(node.left);
int[] right = solve(node.right);
int take = node.val + left[1] + right[1]; // children must stay home
int skip = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // children choose freely
return new int[]{take, skip};
}
}How long it takes: one visit per node, O(n). Extra room: the call stack, O(h). No memo map is needed, because each node passes both numbers up in one go.
Watch out for
- In
skip, do not force the children to be taken. Usemax(take, skip)for each child, because sometimes skipping two floors in a row is best. - If values can be negative, the code still works:
skiplets you pick nothing in a family, which is worth 0. - The empty tree returns 0.
What interviewers ask next
- House robber on a line of houses: the same two numbers, carried from left to right instead of from leaves to root.
- Also return which nodes were picked. Walk down again: at the root take the bigger choice; if a node is taken, its children are skipped; if skipped, each child picks its own bigger choice.
- Largest set of nodes with no two joined, ignoring values (maximum independent set on a tree): the same code with every value equal to 1.
- Smallest set of nodes that touches every line (minimum vertex cover on a tree): also two or three numbers per node, combined from children.
Remember this
Grown-up words: the two numbers per node are the dynamic programming states, solving children before the parent is a postorder depth first search, "no two joined" is an independent set, and this is LeetCode's "house robber III".