96. Unique Binary Search Trees - Explanation
Description
Count the structurally distinct binary search trees that use every integer from 1 through n exactly once. In a binary search tree, all values in a node's left subtree are smaller than that node, and all values in its right subtree are larger. Trees differ when their left/right child structure differs. Return the number of possible trees.
Example 1:
Input: n = 2
Output: 2Explanation: Either 1 is the root with right child 2, or 2 is the root with left child 1. These are the two distinct structures.
Example 2:
Input: n = 4
Output: 14Explanation: Choosing root 1, 2, 3, or 4 yields respectively 5, 2, 2, and 5 valid structures for the remaining subtrees. Their sum is 14.
Example 3:
Input: n = 1
Output: 1Explanation: The only tree is a single node with value 1.
Constraints:
1 <= n <= 19
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Search Tree Properties - Understanding that left subtree values are smaller and right subtree values are larger than the root
- Recursion - Breaking down the problem by choosing each node as the root and solving for left and right subtrees
- Dynamic Programming - Using memoization or bottom-up tabulation to avoid redundant calculations
- Catalan Numbers - Optional but helpful for understanding the mathematical formula behind the count of unique BSTs
1. Recursion
Intuition
To count all unique BSTs with values 1 to n, we need to consider each value as the root. When we choose i as the root, all values less than i must go in the left subtree, and all values greater than i go in the right subtree. The total number of unique BSTs with root i is the product of unique BSTs that can be formed from the left and right subtrees.
This gives us a recursive structure: the count for n nodes equals the sum over all possible roots of (count for left subtree) times (count for right subtree).
Algorithm
- Base case: If
n <= 1, there's exactly one BST (empty tree or single node), so return1. - Initialize
res = 0to accumulate the total count. - For each potential root
ifrom1ton:- Left subtree has
i - 1nodes. - Right subtree has
n - inodes. - Add
numTrees(i - 1) * numTrees(n - i)tores.
- Left subtree has
- Return
res.
Time & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution recalculates the same values multiple times. For instance, numTrees(3) might be computed several times when calculating numTrees(5). We can use memoization to cache results and avoid redundant work.
Algorithm
- Create a hash map or dictionary
dpto store computed results. - Base case: If
n <= 1, return1. - If
nis already indp, return the cached value. - Compute
resby summingnumTrees(i - 1) * numTrees(n - i)for allifrom1ton. - Store
resindp[n]and return it.
Time & Space Complexity
- Time complexity:
- Space complexity:
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of recursion, we can build the solution iteratively from smaller subproblems. Since the count for n nodes depends only on counts for fewer nodes, we compute numTree[0], numTree[1], ..., numTree[n] in order.
Algorithm
- Create an array
numTreeof sizen + 1, initialized with1(base cases for 0 and 1 node). - For each number of
nodesfrom2ton:- Initialize
total = 0. - For each
rootchoice from1tonodes:left = root - 1(nodes in left subtree).right = nodes - root(nodes in right subtree).- Add
numTree[left] * numTree[right]tototal.
- Set
numTree[nodes] = total.
- Initialize
- Return
numTree[n].
class Solution:
def numTrees(self, n: int) -> int:
numTree = [1] * (n + 1)
for nodes in range(2, n + 1):
total = 0
for root in range(1, nodes + 1):
left = root - 1
right = nodes - root
total += numTree[left] * numTree[right]
numTree[nodes] = total
return numTree[n]Time & Space Complexity
- Time complexity:
- Space complexity:
4. Catalan Numbers - I
Intuition
The number of unique BSTs with n nodes is the nth Catalan number. Catalan numbers have a closed-form formula that can be computed directly without iterating through all subproblems. The formula involves calculating a product of fractions.
Algorithm
- Initialize
res = 1. - For
ifrom1ton - 1:- Multiply
resby(n + i + 1). - Divide
resbyi.
- Multiply
- Return
res / n.
Time & Space Complexity
- Time complexity:
- Space complexity: extra space.
5. Catalan Numbers - II
Intuition
Another formula for Catalan numbers uses a recurrence relation: C(n+1) = C(n) * (4n + 2) / (n + 2). This allows us to compute each Catalan number from the previous one with a single multiplication and division.
Algorithm
- Initialize
res = 1. - For
ifrom0ton - 1:- Multiply
resby(4 * i + 2) / (i + 2).
- Multiply
- Return
resas an integer.
Time & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Forgetting the Base Case for Zero Nodes
When implementing the recursive or DP solution, failing to handle the case where n = 0 leads to incorrect results. An empty tree (zero nodes) has exactly one valid structure (the empty structure), so numTrees(0) must return 1. Without this, the multiplication in the recurrence breaks down since multiplying by zero eliminates valid combinations.
Integer Overflow in Catalan Number Formulas
The Catalan number approach involves multiplying large numbers before dividing. In languages like Java, C++, or Go, intermediate products can overflow standard int types even for moderate values of n. Always use long or long long for intermediate calculations, and be careful about the order of operations to minimize overflow risk.
Confusing Node Count with Node Values
The problem asks for the count of structurally unique BSTs with nodes numbered 1 to n, but the actual values do not matter for counting structures. What matters is how many nodes go in the left versus right subtree. Some solutions incorrectly try to track specific node values rather than just counting nodes, leading to unnecessarily complex code or wrong answers.
Sign in to join the discussion