题目描述
解法1-动态规划
解题思路:给定一个有序序列 1 ⋯ n 1\cdots n 1⋯n,为了构建一棵二叉树,我们可以遍历每个数字 i i i,将该数字作为树根,然后将 1 ⋯ ( i − 1 ) 1\cdots (i-1) 1⋯(i−1)作为根结点的左子树,将 ( i + 1 ) ⋯ n (i+1)\cdots n (i+1)⋯n作为根结点的右子树。由于每次遍历根结点是不同的,所以可以保证构造的每棵二叉树都是唯一的。
定义两个函数:
- G ( n ) G(n) G(n):长度为 n n n的序列能构造的不同二叉树的个数;
- F ( i , n ) F(i,n) F(i,n):以 i i i为根、序列长度为 n n n的不同二叉搜索树的个数 ( i ≤ n ) (i\leq n) (i≤n)。
其中, G ( n ) G(n) G(n)就是我们要求的函数。具体求解思路如下:
- 不同的二叉搜索树的总数
G
(
n
)
G(n)
G(n),是对遍历所有
(
i
≤
n
)
(i\leq n)
(i≤n)的
F
(
i
,
n
)
F(i,n)
F(i,n)的总和,即
G ( n ) = ∑ i = 1 n F ( i , n ) G(n)=\sum\limits_{i=1}^n F(i,n) G(n)=i=1∑nF(i,n) - 当
i
i
i为根结点时,其左子树的结点个数为
i
−
1
i-1
i−1个,右结点个数为
n
−
i
n-i
n−i,则
F ( i , n ) = G ( i − 1 ) ∗ G ( n − i ) F(i,n)=G(i-1)*G(n-i) F(i,n)=G(i−1)∗G(n−i) - 综合上面两个公式,可以得到
G ( n ) = ∑ i = 1 n G ( i − 1 ) ∗ G ( n − i ) G(n)=\sum\limits_{i=1}^n G(i-1)*G(n-i) G(n)=i=1∑nG(i−1)∗G(n−i)
从上式可以看到, G ( n ) G(n) G(n)可以由前面的 G ( 1 ) ⋯ G ( n − 1 ) G(1)\cdots G(n-1) G(1)⋯G(n−1)求得。
同时,我们注意到,当 n = 0 n=0 n=0(空树)和 n = 1 n=1 n=1(只有一个结点)时, G ( 0 ) = G ( 1 ) = 1 G(0)=G(1)=1 G(0)=G(1)=1。
class Solution {
public:
int numTrees(int n) {
vector<int> G(n + 1, 0);
G[0] = 1;
G[1] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
}
};
复杂度
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
解法2-卡塔兰数
解题思路:这种解法如果之前没有见过,很难想到。在解法1中推导的
G
(
n
)
G(n)
G(n)函数的值在数学上被称为卡塔兰数
C
n
C_n
Cn:
C
0
=
1
,
C
n
+
1
=
2
(
2
n
+
1
)
n
+
2
C
n
C_0=1,\qquad C_{n+1}=\frac{2(2n+1)}{n+2} C_n
C0=1,Cn+1=n+22(2n+1)Cn
class Solution {
public int numTrees(int n) {
// 提示:我们在这里需要用 long 类型防止计算过程中的溢出
long C = 1;
for (int i = 0; i < n; ++i) {
C = C * 2 * (2 * i + 1) / (i + 2);
}
return (int) C;
}
}
复杂度
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
参考文献
不同的二叉搜索树-官方