难度:中等
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
————————————————————————————————————————————————————————
类似题:
(假设已经完全理解了bst的基础知识)
本题例子给了n=3的情况,可以先从n=1开始思考。
很明显,n=1时只有一个节点,那么输出结果必是1.
n=2时,有两个节点,一个为根,一个必为子节点(可能为左也可能为右)。根据bst的特性,左子树一定小于根节点,右子树一定大于根节点。那么就有两种情况:
1 2
\ /
2 1
(额外问题:如果只是一棵普通的二叉树会有多少种情况?)
那么对于n=3时,根据bst的特性以及对n=2的分析可知,我们需要先确定根节点,再进行排列生成bst。
若根节点为1时,其余的节点都比1要大,所以都在右边,那么对于这颗右子树,又可以选择2或者3作为根节点,而选择2或3本质和n=2的情况又是一样的(有序的n个节点生成bst),这样一来,当根节点为1时,就有两种bst的生成方法。
分别是:
1 1
\ \
2 3
\ /
3 2
再选择2做为根节点,此时再根据bst的特性可以知道,剩下的1,3分别作为左右子树,所以只有一种情况。
2
/ \
1 3
最后再选择3作为根节点,与选择1作为根节点的情况类似,只不过现在是需要在右子树这边进行分析,同理可得:
3 3
/ /
2 1
/ \
1 2
这样,我们就把n=3的所有情况都找出来了。
通过上面分析我们得到两个关键点:
1.必须先选出根节点,再通过bst的特性对左右进行分开讨论;
2.需要用到前n-1次的结果。
第一点有点类似分治的思路,第二点可以dp(动态规划)来满足。
go实现:
func numTrees(n int) int {
if n == 1 {
return 1
}
dp := make([]int,n+1)
dp[0] = 1
dp[1] = 1
dp[2] = 2
for i:=3;i<=n;i++ {
for j:=1;j<i;j++ {
//以j为根
dp[i] += dp[j]*dp[i-j-1]
}
dp[i] += dp[i-1]
}
return dp[n]
}
代码不复杂,前面分析清楚理解之后还是比较好实现的。
for i:=3;i<=n;i++ {
for j:=1;j<i;j++ {
//以j为根
dp[i] += dp[j]*dp[i-j-1]
}
dp[i] += dp[i-1]
}
通过这段代码,我们选择i作为根节点,1...i-1作为右子树的节点,i+1....n作为左子树的节点。
而这些有序的节点,是严格按照bst的特性进行选择的,所以本质上我们只用关注每个子树上节点的个数,对应到dp[i]表示节点个数为i,生成的bst的个数。
第一个for循环,遍历从i=3到i=n的情况,第二个for循环,计算每个i的生成不同bst的个数。
dp[j]表示左边(或者右边)子树中节点的个数,dp[i-j-1]则表示另外一边子树节点的个数。
另外,因为在计算上可以直接把握本质,找子节点个数即可,但是实际上每个节点的数值不同,所以j<i而不是j<=i/2.
dp[i] += dp[i-1]
当i作为根节点时,右子树上节点个数必为i-1个,且摆放方式唯一。
最后即可得想要的结果。