题目来源
题目描述
class Solution {
public:
int numTrees(int n){
}
题目解析
动态规划
分析
给定一个数n,怎么去构建一颗二叉搜索树呢?
- 我们在构建树时,都是先选定根节点,然后在递归的去构建左右子树的。
- 假设给定了一个5,而且对于序列
[1,5]
,我们应该怎么构建树呢?- 假设根节点为5,那么如果要是一颗二叉搜索树,那么剩下的部分
[1, 4]
都必须是它的左子树 - 假设根节点为4,那么如果要是一颗二叉搜索树,那么剩下的部分
[1, 3]
必须是它的左子树,[5]
必须是它的右子树 - 假设根节点为3,那么如果要是一颗二叉搜索树,那么剩下的部分
[1, 2]
必须是它的左子树,[4, 5]
必须是它的右子树 - 假设根节点为2,那么如果要是一颗二叉搜索树,那么剩下的部分
[1]
必须是它的左子树,[3, 5]
必须是它的右子树 - 假设根节点为1,那么如果要是一颗二叉搜索树,那么剩下的部分
[2, 5]
必须是它的右子树
- 假设根节点为5,那么如果要是一颗二叉搜索树,那么剩下的部分
- 对于序列[1,4],我们该怎么构建树呢?
- 假设根节点为4,那么如果要是一颗二叉搜索树,那么剩下的部分
[1, 3]
都必须是它的左子树 - 假设根节点为3,那么如果要是一颗二叉搜索树,那么剩下的部分
[1, 2]
必须是它的左子树,[4,5]
必须是它的右子树 - …
- 假设根节点为4,那么如果要是一颗二叉搜索树,那么剩下的部分
- 从上面可以看出,有大量的重叠子问题,所以可以用递归或者动态规划来解决。
对于上面分析,我们用书面语言来总结一下:
- 给定一个有序序列
1...
n
1...n
1...n,为了构建出一颗二叉树,我们可以遍历每个数字
i
,然后将该数字作为树根,将1...(i-1)
序列作为左子树,将(i + 1) ... n
序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。 - 在上述构造过程中,由于根的值不同,因此我们可以保证每颗二叉搜索树是唯一的。
- 由此可见,原问题可以分解成规模较小的两个子问题,且子问题的解可以复用。因此,我们可以想到使用动态规划来求解本题。
从上面可以看出,对于一个数5,它的最优子结构是:
- 以1为根节点,构建的二叉搜索树有多少个
- 以2为根节点,构建的二叉搜索树有多少个
- 以3为根节点,构建的二叉搜索树有多少个
- 以4为根节点,构建的二叉搜索树有多少个
- 以5为根节点,构建的二叉搜索树有多少个
那么,设以i为根节点的二叉搜索树为f(i),那么其最优子结构和原问题有什么关系呢?假设给定长度为5的序列,那么
- 长度为
5
的序列能构成的不同二叉搜索树的个数 = 以5为根节点,构建的二叉搜索树数量 + 以4为根节点,构建的二叉搜索树数量 + 以3为根节点,构建的二叉搜索树数量 + 以2为根节点,构建的二叉搜索树数量 + 以1为根节点,构建的二叉搜索树数量 =f(1) + f(2) + f(3) + f(4) + f(5)
思路
(1)定义状态
由于题目是要求我们计算不同二叉搜索树的个数,因此我们可以定义两个函数:
G(n)
:长度为n
的序列能构成的不同二叉搜索树的个数f(i)
:以i
为根的二叉搜索树的个数。
则有
G
(
n
)
=
f
(
1
)
+
f
(
2
)
+
f
(
3
)
+
.
.
.
f
(
n
)
G(n)=f(1)+f(2)+f(3)+...f(n)
G(n)=f(1)+f(2)+f(3)+...f(n)
(2)推导公式。关键要推导出f(i)。
- 当i为根节点时,其左子树节点个数为
[1,2,3...i-1]
,右子树节点个数为[i+1, i+2,... n]
- 所以当i为根节点时,其左子树节点个数为
i-1
个,右子树节点为n-i
个,即f(i) = G(i-1)*G(n-i)
- 因此
G ( n ) = G ( 0 ) ∗ G ( n − 1 ) + G ( 1 ) ∗ G ( n − 2 ) + . . . + G ( n − 1 ) ∗ G ( 0 ) G(n) = G(0)*G(n-1)+G(1)*G(n-2)+...+G(n-1)*G(0) G(n)=G(0)∗G(n−1)+G(1)∗G(n−2)+...+G(n−1)∗G(0)
(3)对于边界情况
- f ( 0 ) f(0) f(0),也就是没有节点存在时,此时只有一颗空树,f(0) = 1
- f ( 1 ) f(1) f(1),也就是以1为根节点时,只有节点都为空树,f(1) = 1
- 因此
G ( 0 ) = 1 G(0) = 1 G(0)=1
G ( 1 ) = 1 G(1) = 1 G(1)=1
1 n = 1
2 1 n = 2
/ \
1 2
1 3 3 2 1 n = 3
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
总结
class Solution {
public:
int numTrees(int n) {
std::vector<int> dp(n + 1);
dp[0] = 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];
}
// dp[2] = dp[0] * dp[1] + dp[1] * dp[0];
}
return dp[n];
}
}