文章目录
题目
标题和出处
标题:不同的二叉搜索树 II
难度
5 级
题目描述
要求
给定一个整数 n \texttt{n} n,返回所有由 n \texttt{n} n 个结点组成、结点值从 1 \texttt{1} 1 到 n \texttt{n} n 且结点值各不相同的不同二叉搜索树 。可以按任意顺序返回答案。
示例
示例 1:

输入: n = 3 \texttt{n = 3} n = 3
输出: \[1,null,2,null,3,1,null,3,2,2,1,3,3,1,null,null,2,3,2,null,1] \texttt{\[1,null,2,null,3,1,null,3,2,2,1,3,3,1,null,null,2,3,2,null,1]} \[1,null,2,null,3,1,null,3,2,2,1,3,3,1,null,null,2,3,2,null,1]
示例 2:
输入: n = 1 \texttt{n = 1} n = 1
输出: \[1] \texttt{\[1]} \[1]
数据范围
- 1 ≤ n ≤ 8 \texttt{1} \le \texttt{n} \le \texttt{8} 1≤n≤8
解法
思路和算法
为了构造二叉搜索树,需要确定二叉搜索树的结点值范围,并确定根结点的值,然后分别构造根结点的左子树和右子树。这是一个递归分治的过程。
由 n n n 个结点组成的二叉搜索树的结点值范围是 1 , n 1, n 1,n。用 rootVal \textit{rootVal} rootVal 表示根结点值,则 1 ≤ rootVal ≤ n 1 \le \textit{rootVal} \le n 1≤rootVal≤n,根结点的左子树的结点值范围是 1 , rootVal − 1 1, \\textit{rootVal} - 1 1,rootVal−1,根结点的右子树的结点值范围是 rootVal + 1 , n \\textit{rootVal} + 1, n rootVal+1,n。对于每个根结点值 rootVal \textit{rootVal} rootVal,其左子树和右子树分别可能有多种情况,根结点的左子树和右子树分别可以是任意一个符合要求的左子树和右子树。
用 start , end \\textit{start}, \\textit{end} start,end 表示二叉搜索树的结点值范围。分治的终止条件是结点值范围为空,此时符合结点值范围的只有空二叉搜索树 null \text{null} null。当结点值范围不为空时,分别将每个结点值作为根结点值,然后递归地构造左子树和右子树。
当根结点值为 rootVal \textit{rootVal} rootVal 时,用 leftSubtrees \textit{leftSubtrees} leftSubtrees 和 rightSubtrees \textit{rightSubtrees} rightSubtrees 分别表示符合要求的左子树列表和右子树列表,对于任意 leftSubtree ∈ leftSubtrees \textit{leftSubtree} \in \textit{leftSubtrees} leftSubtree∈leftSubtrees 和 rightSubtree ∈ rightSubtrees \textit{rightSubtree} \in \textit{rightSubtrees} rightSubtree∈rightSubtrees,将 leftSubtree \textit{leftSubtree} leftSubtree 和 rightSubtree \textit{rightSubtree} rightSubtree 分别作为根结点的左子树和右子树,即可得到一个符合要求的二叉搜索树。特别地,当 rootVal = 1 \textit{rootVal} = 1 rootVal=1 时必有 leftSubtree = null \textit{leftSubtree} = \text{null} leftSubtree=null,当 rootVal = n \textit{rootVal} = n rootVal=n 时必有 rightSubtree = null \textit{rightSubtree} = \text{null} rightSubtree=null。
代码
java
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new ArrayList<TreeNode>();
} else {
return generateTrees(1, n);
}
}
public List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> trees = new ArrayList<TreeNode>();
if (start > end) {
trees.add(null);
} else {
for (int rootVal = start; rootVal <= end; rootVal++) {
List<TreeNode> leftSubtrees = generateTrees(start, rootVal - 1);
List<TreeNode> rightSubtrees = generateTrees(rootVal + 1, end);
for (TreeNode leftSubtree : leftSubtrees) {
for (TreeNode rightSubtree : rightSubtrees) {
TreeNode root = new TreeNode(rootVal);
root.left = leftSubtree;
root.right = rightSubtree;
trees.add(root);
}
}
}
}
return trees;
}
}
复杂度分析
-
时间复杂度: O ( 4 n n ) O(\dfrac{4^n}{\sqrt{n}}) O(n 4n),其中 n n n 是二叉搜索树的结点数。由 n n n 个结点组成的二叉搜索树的数量是第 n n n 个卡特兰数 C 2 n n n + 1 \dfrac{C_{2n}^n}{n + 1} n+1C2nn,其渐进上界为 O ( 4 n n n ) O(\dfrac{4^n}{n \sqrt{n}}) O(nn 4n),对于每个二叉搜索树需要 O ( n ) O(n) O(n) 的时间生成和添加到答案中,因此时间复杂度是 O ( 4 n n ) O(\dfrac{4^n}{\sqrt{n}}) O(n 4n)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。递归调用栈需要 O ( n ) O(n) O(n) 的空间。注意返回值不计入空间复杂度。
后记
此处提供的解法为分治。由于同一个结点值范围的子树可能被多次计算,因此可以使用记忆化。这道题中的结点数不超过 8 8 8,因此使用记忆化并不能显著提升性能。
读者可以自行尝试使用记忆化的实现。