To generate all unique BSTs with values from 1 to n, we pick each value as the root and recursively generate all possible left and right subtrees. When val is the root, values 1 to val-1 form the left subtree and values val+1 to n form the right subtree. We combine every leftTree with every rightTree to create all unique trees with that root.
generate(left, right) that returns a list of all unique BSTs using values from left to right.left > right, return a list containing null (representing an empty subtree).val from left to right:generate(left, val - 1).generate(val + 1, right).leftTree and rightTree, create a new tree with val as root and add it to res.res.generate(1, n) to get the final result.# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def generateTrees(self, n: int) -> List[Optional[TreeNode]]:
def generate(left, right):
if left > right:
return [None]
res = []
for val in range(left, right + 1):
for leftTree in generate(left, val - 1):
for rightTree in generate(val + 1, right):
root = TreeNode(val, leftTree, rightTree)
res.append(root)
return res
return generate(1, n)The recursive solution recomputes the same ranges multiple times. For example, generate(2, 4) might be called from different parent recursions. We can cache results for each (left, right) pair to avoid regenerating the same subtrees.
dp to store results for each (left, right) pair.generate(left, right) similar to the basic recursive approach.dp[left][right] already has a value. If so, return the cached list.res in dp[left][right].generate(1, n).# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def generateTrees(self, n: int) -> List[Optional[TreeNode]]:
dp = {}
def generate(left, right):
if left > right:
return [None]
if (left, right) in dp:
return dp[(left, right)]
res = []
for val in range(left, right + 1):
for leftTree in generate(left, val - 1):
for rightTree in generate(val + 1, right):
root = TreeNode(val, leftTree, rightTree)
res.append(root)
dp[(left, right)] = res
return res
return generate(1, n)We can build the solution iteratively by computing all BSTs for smaller ranges first. We process ranges by increasing length: first all ranges of length 1, then length 2, and so on up to length n. This ensures that when we compute dp[left][right], all smaller subproblems are already solved.
dp[left][right] to store all BSTs for each range.i, set dp[i][i-1] = [null] (empty subtree).length from 1 to n:left where left + length - 1 <= n:right = left + length - 1.val from left to right:dp[left][val-1] and dp[val+1][right].dp[left][right].dp[1][n].# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def generateTrees(self, n: int) -> List[Optional[TreeNode]]:
dp = [[[] for _ in range(n + 2)] for _ in range(n + 2)]
for i in range(1, n + 2):
dp[i][i - 1] = [None]
for length in range(1, n + 1):
for left in range(1, n - length + 2):
right = left + length - 1
dp[left][right] = []
for val in range(left, right + 1):
for leftTree in dp[left][val - 1]:
for rightTree in dp[val + 1][right]:
root = TreeNode(val, leftTree, rightTree)
dp[left][right].append(root)
return dp[1][n]The number of unique BST structures depends only on the count of nodes, not their actual values. We can generate all BST structures for sizes 0, 1, 2, ..., n and then adjust node values using a shift function. For a tree structure with nodes 1 to k, we can create a tree with nodes offset+1 to offset+k by adding offset to each node's value.
dp[length] to store all BST structures for trees with length nodes.dp[0] = [null] (empty tree).length from 1 to n:val from 1 to length:val - 1 nodes (from dp[val-1]).length - val nodes (from dp[length-val]), but needs value shifting.leftTree from dp[val-1] with shift(rightTree, val) from dp[length-val].shift(node, offset) function that creates a new tree with all values increased by offset.dp[n].# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def generateTrees(self, n: int) -> list[Optional[TreeNode]]:
dp = [[] for _ in range(n + 1)]
dp[0] = [None]
for length in range(1, n + 1):
dp[length] = []
for val in range(1, length + 1):
for leftTree in dp[val - 1]:
for rightTree in dp[length - val]:
root = TreeNode(val)
root.left = leftTree
root.right = self.shift(rightTree, val)
dp[length].append(root)
return dp[n]
def shift(self, node: Optional[TreeNode], offset: int) -> Optional[TreeNode]:
if not node:
return None
root = TreeNode(node.val + offset)
root.left = self.shift(node.left, offset)
root.right = self.shift(node.right, offset)
return rootWhen the range is invalid (left > right), the base case should return a list containing null (representing an empty subtree), not an empty list. Returning an empty list causes the nested loops to skip all combinations, producing no trees at all.
In the space-optimized approach, left subtrees are shared across multiple result trees since they have identical structure and values. However, right subtrees need to be cloned with shifted values. Forgetting to create new nodes when shifting values causes all trees to share and mutate the same nodes incorrectly.
When using the space-optimized DP approach, right subtree values must be shifted by the root value. A common mistake is applying the wrong offset or forgetting to recursively shift all nodes in the subtree. Each node in the right subtree should have offset added to its value.