Skip to content
dsa450for five year olds

Dynamic Programming · 39 of 60

Largest Independent Set Problem

Invite as many people from a family tree as you can, but never invite a parent together with their own child.

Simple way
time O(2^n)
room O(h)
Clever way
time O(n)
room O(h)

The problem, simply

You get a binary tree. Choose as many nodes as you can, with one rule: no chosen node may be the parent or child of another chosen node. Grandparent and grandchild are fine, and so are brothers and sisters.

Give back how many nodes you can choose. A group where no two are joined by a branch is called an independent set, so this is the largest independent set of a tree.

A story to remember it

Picture it

tree
1020304050607080

Eight people in a family tree. The marked ones, 10, 40, 70, 80 and 60, form the biggest group with no parent and child together.

  • answer
  • ignored

Examples

A normal case

In
[10, 20, 30, 40, 50, null, 60, null, null, 70, 80]
Out
5

Invite 10, 40, 70, 80 and 60. Inviting 20 instead would block 10, 40 and 50.

A smaller tree

In
[10, 20, 30, 40, 50, null, 60]
Out
4

Invite 10, 40, 50 and 60. 10 is not the parent of any of them, only their grandparent.

A straight line

In
[1, 2, null, 3, null, 4]
Out
2

A chain of 4 nodes: take every second one, like 1 and 3, or 2 and 4.

One node

In
[7]
Out
1

Invite the only person.

Empty tree

In
[]
Out
0

No people, no party.

Way 1: the simple way

For each node, try both choices and keep the better one.

  1. If the node is empty, the answer is 0.
  2. Skip the node: answer for the left child plus answer for the right child.
  3. Invite the node: 1, plus the answers for all four grandchildren (the children must stay home).
  4. Return the bigger of the two.
tree
kkkkkggkkggg10204050708070803060405060againagainagainagainagain

The calls the plain recursion makes. A branch marked k asks a child (when the node stays home), g asks a grandchild (when the node is invited). Circles marked again were already asked on another branch. Every grandchild is asked once by its parent and once more by its grandparent, and in a tall tree those repeats pile up.

  • rejected
java
class Solution {
    public int lisBrute(TreeNode root) {
        if (root == null) return 0;

        int skip = lisBrute(root.left) + lisBrute(root.right);   // ask the children

        int invite = 1;                                           // ask the grandchildren
        if (root.left != null) invite += lisBrute(root.left.left) + lisBrute(root.left.right);
        if (root.right != null) invite += lisBrute(root.right.left) + lisBrute(root.right.right);

        return Math.max(skip, invite);
    }
}

How long it takes: each node is solved once by its parent and again by its grandparent, and those repeats multiply as you go down. On a long thin tree the count grows like the Fibonacci numbers, which is exponential, O(2^n) as an upper bound. Extra room: the call chain is as deep as the tree, O(h). Adding a memo map from node to answer brings it down to O(n).

Way 2: the clever way

Do one walk where children report before parents (post order), and let every node return two numbers.

  1. An empty node returns in = 0, out = 0.
  2. For a node, first get the reports from its left and right child.
  3. in = 1 + left.out + right.out. I am invited, so my children stay home.
  4. out = max(left.in, left.out) + max(right.in, right.out). I stay home, so each child does what is best for its own branch.
  5. The answer is max(root.in, root.out).

Each node reports in and out to its parent

01 / 10

tree
1020304050607080start
gridreports, in post order
4070805020603010inout

Visit children before parents. Each node will report two numbers: "in" is the biggest group if this node is invited, "out" is the biggest group if it is not.

  • looking now
  • checking
  • done
  • answer
  • just added
  • ignored
java
class Solution {
    // returns {in, out}: best size if this node is invited, and if it is not
    private int[] solve(TreeNode node) {
        if (node == null) return new int[] {0, 0};
        int[] l = solve(node.left);               // children report first
        int[] r = solve(node.right);
        int in = 1 + l[1] + r[1];                 // invited: children stay home
        int out = Math.max(l[0], l[1]) + Math.max(r[0], r[1]); // skipped: children choose
        return new int[] {in, out};
    }

    public int largestIndependentSet(TreeNode root) {
        int[] res = solve(root);
        return Math.max(res[0], res[1]);
    }
}

How long it takes: every node is visited once and does a few additions, O(n). Extra room: the call chain is as deep as the tree, O(h), which is O(n) for a tree shaped like a line and O(log n) for a balanced one.

Watch out for

  • Grandparent and grandchild may both be invited. Only direct parent and child are forbidden.
  • Empty tree: return 0. The null case returning {0, 0} handles this.
  • A very deep tree can overflow the Java call stack. Use an explicit stack and a post order loop if the tree can be a long chain of 100,000 nodes.

What interviewers ask next

  • Return the people, not just the count. Walk down from the root: if a node is allowed and in >= out, take it and mark its children "not allowed"; otherwise move on to its children with "allowed".
  • House Robber III: same tree, but each node has money and you maximize the sum. Replace 1 + with node.val +.
  • What about a general graph? Largest independent set is NP hard on general graphs. Trees are easy because cutting a node splits the problem into separate branches.
  • Smallest vertex cover of a tree: it is n minus the largest independent set, or its own DP: in = 1 + sum of min(child in, child out), out = sum of child in.
  • Nodes with many children (an n-ary tree): same two numbers, sum over all children.

Remember this

Grown-up words: the party group is an independent set, the family tree is a binary tree, children reporting before parents is a post order traversal, and the two numbers per node are the states of tree dynamic programming.