二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树数据结构,它满足以下性质:
给定节点数量n时,不同结构的BST数量计算是一个经典的组合数学问题。这个问题看似简单,但蕴含着动态规划和递归思想的精妙应用。
注意:虽然节点值从1到n,但由于BST的结构只与节点数量有关,与具体数值无关,所以计算时可以忽略具体数值。
对于n个节点的BST,我们可以考虑每个节点作为根节点的情况。假设我们选择第i个节点作为根节点(1 ≤ i ≤ n),那么:
因此,以i为根节点的BST数量等于左子树的可能数量乘以右子树的可能数量。将所有可能的根节点情况相加,就得到总数G(n):
G(n) = Σ G(i-1) * G(n-i) (i从1到n)
这个递推关系就是著名的卡塔兰数(Catalan Number)的递推公式。
java复制public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1; // 空树算一种情况
dp[1] = 1; // 单节点树
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
java复制public static void main(String[] args) {
System.out.println(numTrees(3)); // 输出应为5
System.out.println(numTrees(1)); // 输出应为1
System.out.println(numTrees(4)); // 输出应为14
System.out.println(numTrees(5)); // 输出应为42
}
可以在内层循环后添加打印语句,观察中间结果:
java复制for (int i = 2; i <= n; i++) {
System.out.println("计算i=" + i);
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
System.out.printf("j=%d: dp[%d] += dp[%d]*dp[%d] = %d*%d\n",
j, i, j-1, i-j, dp[j-1], dp[i-j]);
}
System.out.println("dp[" + i + "] = " + dp[i]);
}
卡塔兰数有直接计算公式:
Cₙ = (2n)! / (n!(n+1)!)
可以用此公式直接计算结果,但需要注意大数计算和整数溢出问题。
java复制public int numTrees(int n) {
int[] memo = new int[n + 1];
Arrays.fill(memo, -1);
memo[0] = 1;
memo[1] = 1;
return helper(n, memo);
}
private int helper(int n, int[] memo) {
if (memo[n] != -1) return memo[n];
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += helper(i - 1, memo) * helper(n - i, memo);
}
memo[n] = sum;
return sum;
}
可以观察到dp[i]只依赖于前面的结果,可以尝试优化空间复杂度,但在这个问题中优化空间不大。
当n较大时(虽然题目限制n≤19),中间结果可能会溢出。可以使用long类型存储中间结果:
java复制long[] dp = new long[n + 1];
容易忽略dp[0]=1的情况,这是递推的基础。如果设为0,会导致所有结果都为0。
外层循环必须从小到大计算,因为大数的结果依赖于小数的结果。
这个问题虽然看似理论化,但有实际应用价值:
对于第一个问题,可以使用递归构造所有可能的树结构:
java复制public List<TreeNode> generateTrees(int n) {
if (n == 0) return new ArrayList<>();
return generate(1, n);
}
private List<TreeNode> generate(int start, int end) {
List<TreeNode> trees = new ArrayList<>();
if (start > end) {
trees.add(null);
return trees;
}
for (int i = start; i <= end; i++) {
List<TreeNode> leftTrees = generate(start, i - 1);
List<TreeNode> rightTrees = generate(i + 1, end);
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
TreeNode root = new TreeNode(i);
root.left = left;
root.right = right;
trees.add(root);
}
}
}
return trees;
}
这个实现展示了如何实际生成所有可能的BST结构,而不仅仅是计算数量。