Skip to content
dsa450for five year olds

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

tree
192348

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.

tree
take 1take 1take 1skip 1skip 9skip 9skip 1skip 2f(1)f(3)f(4)f(8)f(9)f(3)f(4)f(2)f(8)

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
java
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, so take = my value + left.skip + right.skip.
  • skip: the best total if I am not picked. Each child may do what is best for it, so skip = 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

tree
192348
stackhelpers waiting
solve(1)top

Start at the root. Nobody can decide before hearing from their children, so go down first.

  • looking now
  • done
  • answer
  • ignored
java
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. Use max(take, skip) for each child, because sometimes skipping two floors in a row is best.
  • If values can be negative, the code still works: skip lets 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".