Unique Binary Search Trees II

MediumTree DP · Divide & Conquer on Ranges🧩 Tree & Graph DPLeetCode #95 ↗Updated

Problem

Given n, return every structurally different binary search tree whose nodes hold exactly the values 1..n, in any order.
Inputn = 1Output[[1]]
Inputn = 3Output5 trees, e.g. [2,1,3]

1 ≤ n ≤ 8  ·  answer size = Catalan number Cn (C8 = 1430)

Try it yourself

Java
class Solution {
    public List<TreeNode> generateTrees(int n) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Fix the root value i: the BST property forces the left subtree to use exactly [lo, i-1] and the right [i+1, hi]; pair every left option with every right option.
PatternTree & Graph DP

Instinct A node's best answer depends only on its children's answers - post-order DFS that returns a tiny tuple of states per node.

Memory hack Kids report up, parent decides - every subtree hands its parent a small report card, and the parent never re-reads the kids' homework.

Open the Tree & Graph DP guide →

Approaches

Brute ForceAll Insertion Orders + Dedupe

Time O(n!·n2)Space O(n·Cn)

Every BST on 1..n is produced by inserting some permutation of the values. Insert all n! permutations and keep one tree per distinct pre-order signature; many orders give the same shape.

Java
public List<TreeNode> generateTrees(int n) {
    Map<String, TreeNode> unique = new LinkedHashMap<>();
    permute(new int[n], new boolean[n + 1], 0, unique);
    return new ArrayList<>(unique.values());
}
private void permute(int[] order, boolean[] used, int pos, Map<String, TreeNode> unique) {
    if (pos == order.length) {
        TreeNode root = null;
        for (int v : order) root = insert(root, v);
        unique.putIfAbsent(key(root), root);
        return;
    }
    for (int v = 1; v < used.length; v++) {
        if (used[v]) continue;
        used[v] = true; order[pos] = v;
        permute(order, used, pos + 1, unique);
        used[v] = false;
    }
}
private TreeNode insert(TreeNode node, int v) {
    if (node == null) return new TreeNode(v);
    if (v < node.val) node.left = insert(node.left, v);
    else node.right = insert(node.right, v);
    return node;
}
private String key(TreeNode node) { // pre-order shape signature
    return node == null ? "#" : node.val + "," + key(node.left) + "," + key(node.right);
}

Obviously correct, but factorial work for a Catalan-sized answer.

At a glance

LevelApproachTimeSpace
Brute ForceAll Insertion Orders + DedupeO(n!·n2)O(n·Cn)
BetterRecursion on Value RangesO(3n + Cn)O(Cn)
OptimalMemoized Ranges (Top-Down DP)O(n3 + Cn)O(n2 + Cn)
Common pitfalls
  • Empty range must give [null], not [], or no pairs form.
  • Reusing one root object across (left, right) pairs.
  • Mixing it up with Unique BSTs I, which only counts.
Interview tips
  • Argue the root split: BST order fixes each subtree's range.
  • Output size Cn is a hard lower bound on time.
  • Mention shared subtrees; deep-copy if callers mutate.
Pattern
Range DP / divide & conquer on roots → enumerate or count structures built from a split point (BSTs, parenthesisations).
My notes saved in this browser