一、问题描述:
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
二、解题思路:
二叉树是由根节点,左右子树组成的,二叉搜索树要满足左子树的所有元素都小于根节点,右子树的所有节点大于根节点。
这样就可以把一个问题划分为多个小问题(在根节点确定的时候,左右子树共有多少种情况),符合动态规划的思想。
假设共有 n 个节点,让 n 个节点依次轮流充当根节点,找到此时左右子树可能的排列情况,结果就是累加起来的和。
三、图解分析
- n=1 和 n=2 时:
不难看出一个节点时,只有一种情况。
两个节点时,以1为根节点,左子树为空。以2为根节点,右子树为空,共有两种情况。
- 以此类推,n=3 时有:
①以1为根节点,左子树为空,右子树有两个节点,所以共有2种情况。
②以2为根节点,左子树有一个节点,右子树有一个节点,所以共有1种情况。
③以3为根节点,左子树有两个节点,右子树为空,所以共有2种情况。
共有5种情况。
四、代码实现
//时间复杂度为 O(n^2)
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= i; j++){
dp[i] += dp[i - j] * dp[j - 1];
}
}
//for(int num : dp) System.out.println(num);//打印dp数组看一看与递推的结果是否一致
return dp[n];
}
}
- 时间复杂度分析:外层循环需要遍历 n 次,内层循环每次需要遍历最多 i 次。因此,总体时间复杂度为 O(n^2)。
五、补充
- 动态规划详解
①dp数组以及下标的含义:
定义dp数组初始化长度为 n+1。dp[i] 表示 i 个节点可以组成 dp[i] 种二叉搜索树。
②递推公式:
dp[i] += dp[j - 1] * dp[i - j];
j 相当于是头结点的元素,从1遍历到 i 为止。
j-1 为 j 为头结点左子树节点数量,i - j 为以 j 为头结点右子树节点数量。
③dp数组初始化
dp[0] = 1。也可以加上dp[1] = 1;
虽然题目写了n > 0,但是二叉树的左右子树可能是空的,dp[0]不能写成0,否则结果都变成0了,为空时也当成是一种排列方式。
④确定遍历顺序
n 个节点的结果dp[n]要依赖 dp[1] ~ dp[i] 的值,所以先遍历 1 ~ n 里面每一个数作为头结点的状态,用 i 来遍历。
根据递推公式dp[i] += dp[j - 1] * dp[i - j],节点数为 i 的 dp[i] 是依靠 i 之前节点数的状态。所以第二层遍历 1 ~ i 里面每一个数作为头结点的状态,用 j 来遍历。简单带入 n = 3的情况,递推公式的结果与图中的推论是一致的。
⑤核心代码如下:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
}
}