class Solution:
def numTrees(self, n: int) -> int:
# dp[i] will store the number of unique BSTs with i nodes
dp = [0] * (n + 1)
dp[0] = 1 # Empty tree
dp[1] = 1 # Single node tree
for nodes in range(2, n + 1):
for root in range(1, nodes + 1):
left = root - 1
right = nodes - root
dp[nodes] += dp[left] * dp[right]
return dp[n]
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
dp[1] = 1;
for (int nodes = 2; nodes <= n; ++nodes) {
for (int root = 1; root <= nodes; ++root) {
int left = root - 1;
int right = nodes - root;
dp[nodes] += dp[left] * dp[right];
}
}
return dp[n];
}
};
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int nodes = 2; nodes <= n; nodes++) {
for (int root = 1; root <= nodes; root++) {
int left = root - 1;
int right = nodes - root;
dp[nodes] += dp[left] * dp[right];
}
}
return dp[n];
}
}
var numTrees = function(n) {
let dp = new Array(n + 1).fill(0);
dp[0] = 1;
dp[1] = 1;
for (let nodes = 2; nodes <= n; nodes++) {
for (let root = 1; root <= nodes; root++) {
let left = root - 1;
let right = nodes - root;
dp[nodes] += dp[left] * dp[right];
}
}
return dp[n];
};
Given an integer n
, find how many structurally unique binary search trees (BSTs) can be formed using all the values from 1
to n
. Each value from 1
to n
must be used exactly once in each BST. The structure of the BSTs must be unique, not just the values.
n
values must be used in each tree, with no repetitions.n
nodes.
At first glance, it seems like we could try to generate all possible BSTs with n
nodes and count them. However, this brute-force approach quickly becomes infeasible as n
increases, since the number of trees grows rapidly.
Instead, we should look for a pattern or recursive relationship. Since the root of the BST can be any value between 1
and n
, and the left and right subtrees are themselves BSTs with the remaining numbers, we can break the problem into smaller subproblems.
The key insight is to realize that the number of unique BSTs for n
nodes is determined by the number of unique BSTs on the left and right of each possible root. This leads us toward a dynamic programming or recursive solution.
dp[i]
represent the number of unique BSTs that can be constructed with i
nodes.
dp[0] = 1
(an empty tree is a valid BST)dp[1] = 1
(a single node is a valid BST)root
from 1
to n
, consider it as the root of the BST.
root - 1
, and in the right subtree is n - root
.
n
nodes and root
as the root is dp[root-1] * dp[n-root]
.
dp[n]
.
dp
array iteratively from 2
to n
using the above relation.
dp[n]
as the result.
This approach avoids redundant computation by storing results for subproblems, making it very efficient.
Let's work through an example with n = 3
:
dp[0] = 1
), Right subtree: 2 nodes (dp[2]
)dp[1] = 1
), Right subtree: 1 node (dp[1] = 1
)dp[2]
), Right subtree: 0 nodes (dp[0] = 1
)dp[2]
first:
1 * 1 = 1
1 * 1 = 1
dp[2] = 2
dp[3]
:
1 * 2 = 2
1 * 1 = 1
2 * 1 = 2
2 + 1 + 2 = 5
n
.
O(n^2)
. For each i
from 2 to n
, we loop through all possible roots (up to i
), so the total work is proportional to the sum 1 + 2 + ... + n = O(n^2)
.
O(n)
, since we store dp[0]
to dp[n]
.
The Unique Binary Search Trees problem is a classic example of using dynamic programming to count combinatorial structures efficiently. By recognizing the recursive structure of BSTs and storing subproblem results, we avoid redundant computations and achieve a solution that scales well even for large n
. The solution is elegant because it leverages the properties of BSTs and dynamic programming to reduce a seemingly complex problem to a simple iterative process.