Largest independent subset in a tree
Problem
Given a binary tree, find the size of the largest independent subset (LISS). A subset of nodes is independent if no two nodes in the subset are directly connected by an edge.
Examples
Example 1
Input:
root = [9,5,11,3,7,null,10,null,4]
Output: 4
Explanation: One maximum independent subset is [9,3,7,10]. No two chosen nodes are directly connected. Other optimal subsets of size 4 also exist (e.g. [5,11,4,7] is NOT valid because 5-7 are adjacent; [5,11,4] has size 3). Size 4 is the maximum.
Array representation is level-order (null for absent children). The tree:
9
/ \
5 11
/ \ \
3 7 10
\
4
Some valid independent subsets: [9,3,7,10], [5,11,4] (size 3). The largest has size 4.
Example 2
Input:
root = [1]
Output: 1
Explanation: Single node; we can take it.
Example 3
Input:
root = [2,1,3]
Output: 2
Explanation: Optimal sets are [1,3] (size 2). We cannot take 2 together with 1 or 3 because they are adjacent.
Related LeetCode problem: not an exact match, but [House Robber III Problem](house-robber-3-houses-in-binary-tree) is closely related (tree DP with non-adjacent constraint, maximizing sum instead of count).
Solution
We present two methods:
- Method 1 — Naive recursion (exponential) to illustrate the idea.
- Method 2 — Optimal O(n) DP using two-state per node (
include/exclude) with a single post-order traversal.
Method 1 — Naive recursion (include grandchildren)
Intuition
If we include a node v in the independent set, we cannot include its immediate children, but we may include its grandchildren. If we exclude v we are free to include its children. The naive recursion compares these two choices.
Approach
- If
rootis null return0. - Compute
sizeIncluding = 1 + sum(liss(g) for g in grandchildren of root). - Compute
sizeExcluding = liss(root.left) + liss(root.right). - Return
max(sizeIncluding, sizeExcluding).
This recalculates many subproblems and runs exponentially in the worst case.
Code
C++
class Solution {
public:
int liss(TreeNode* root) {
if (!root) return 0;
int incl = 1;
if (root->left) {
if (root->left->left) incl += liss(root->left->left);
if (root->left->right) incl += liss(root->left->right);
}
if (root->right) {
if (root->right->left) incl += liss(root->right->left);
if (root->right->right) incl += liss(root->right->right);
}
int excl = liss(root->left) + liss(root->right);
return max(incl, excl);
}
};
Go
package main
func liss(root *TreeNode) int {
if root == nil { return 0 }
incl := 1
if root.Left != nil {
if root.Left.Left != nil { incl += liss(root.Left.Left) }
if root.Left.Right != nil { incl += liss(root.Left.Right) }
}
if root.Right != nil {
if root.Right.Left != nil { incl += liss(root.Right.Left) }
if root.Right.Right != nil { incl += liss(root.Right.Right) }
}
excl := liss(root.Left) + liss(root.Right)
if incl > excl { return incl }
return excl
}
Java
class Solution {
public int liss(TreeNode root) {
if (root == null) return 0;
int incl = 1;
if (root.left != null) {
if (root.left.left != null) incl += liss(root.left.left);
if (root.left.right != null) incl += liss(root.left.right);
}
if (root.right != null) {
if (root.right.left != null) incl += liss(root.right.left);
if (root.right.right != null) incl += liss(root.right.right);
}
int excl = liss(root.left) + liss(root.right);
return Math.max(incl, excl);
}
}
Python
from typing import Optional
class Solution:
def liss(self, root: Optional[TreeNode]) -> int:
if root is None: return 0
incl = 1
if root.left:
if root.left.left: incl += self.liss(root.left.left)
if root.left.right: incl += self.liss(root.left.right)
if root.right:
if root.right.left: incl += self.liss(root.right.left)
if root.right.right: incl += self.liss(root.right.right)
excl = self.liss(root.left) + self.liss(root.right)
return max(incl, excl)
Complexity
- ⏰ Time complexity:
O(2^n)in the worst case (exponential) — repeated overlapping subproblems. - 🧺 Space complexity:
O(h)recursion stack,his tree height.
Method 2 — Optimal DP (two-state per node)
Intuition
For each node v compute two values: incl[v] = size of largest independent set of subtree rooted at v when v is included; excl[v] = size when v is excluded. Then:
incl[v] = 1 + sum(excl[child] for child in children)(ifvis included, children cannot be included)excl[v] = sum(max(incl[child], excl[child]) for child in children)(ifvis excluded, each child may be included or excluded)
A post-order traversal computes these values in O(n) time.
Approach
- Do a post-order DFS. For each node
vcompute and return(incl, excl)as described. - Answer for the whole tree is
max(incl[root], excl[root]). - This visits each node once and performs O(1) work per node.
Code
C++
class Solution {
public:
pair<int,int> dfs(TreeNode* node) {
if (!node) return {0,0};
auto L = dfs(node->left);
auto R = dfs(node->right);
int incl = 1 + L.second + R.second; // include node -> children excluded
int excl = max(L.first, L.second) + max(R.first, R.second); // exclude node
return {incl, excl};
}
int liss(TreeNode* root) {
auto res = dfs(root);
return max(res.first, res.second);
}
};
Go
package main
func dfs(node *TreeNode) (int,int) {
if node == nil { return 0,0 }
lf, le := dfs(node.Left)
rf, re := dfs(node.Right)
incl := 1 + le + re
excl := max(lf, le) + max(rf, re)
return incl, excl
}
func liss(root *TreeNode) int {
a,b := dfs(root)
return max(a,b)
}
func max(a,b int) int { if a>b { return a }; return b }
Java
class Solution {
private int[] dfs(TreeNode node) {
if (node == null) return new int[]{0,0};
int[] L = dfs(node.left);
int[] R = dfs(node.right);
int incl = 1 + L[1] + R[1];
int excl = Math.max(L[0], L[1]) + Math.max(R[0], R[1]);
return new int[]{incl, excl};
}
public int liss(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
}
Python
from typing import Tuple, Optional
class Solution:
def dfs(self, node: Optional[TreeNode]) -> Tuple[int,int]:
if not node: return (0,0)
li, le = self.dfs(node.left)
ri, re = self.dfs(node.right)
incl = 1 + le + re
excl = max(li, le) + max(ri, re)
return (incl, excl)
def liss(self, root: Optional[TreeNode]) -> int:
incl, excl = self.dfs(root)
return max(incl, excl)
Complexity
- ⏰ Time complexity:
O(n)— single post-order traversal visiting each node once. - 🧺 Space complexity:
O(h)for recursion stack (orO(n)worst-case for a skewed tree), plusO(1)auxiliary per node if values are returned rather than stored.
Created at: 2018-02-07T09:21:59+01:00 Updated at: 2018-02-07T09:21:59+01:00