1. 题目
原题链接
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
1.1. 要点
- 掌握二叉搜索树
- 假定每一个数字为根结点,并将剩余的数组分配到左右两边继续构造子树。
此时每一个根结点构成二叉树的数量总和为左边*右边。
1.2. 递归
这个版本的实现也是勉勉强强的通过的LeetCode,但是性能非常差。
但是毕竟用了二叉树递归特性来实现,所以还是体会一下吧。
1.2.1. 代码片段
func numTrees(n int) int {
if n == 0 {
return 0
}
var numTreesMap = make(map[string]int, 0) //如果不用一个hash map来记录已经运算过的结果,直接就导致运算超时。
return numTreesHelper(1, n, numTreesMap)//注意边界位置[1...n]
}
func numTreesHelper(left, right int, numTreesMap map[string]int) int {
if left >= right { //由于边界的设定是允许[1...1]这个区间直接返回1种情况。强调一下在编写整个算法过程中,如何保证 循环不变量(loop invariant) 是一个非常重要的过程
return 1
}
var sum int
for i := left; i <= right; i++ {
lkey := fmt.Sprintf("%d_%d", left, i-1)
rkey := fmt.Sprintf("%d_%d", i+1, right)
var l,r int
if v, ok := numTreesMap[lkey]; ok { //运算过的直接返回不需要再递归了
l = v
}else{
l = numTreesHelper(left, i-1, numTreesMap)
numTreesMap[lkey] = l
}
if v, ok := numTreesMap[rkey]; ok {
r = v
}else{
r = numTreesHelper(i+1, right, numTreesMap)
numTreesMap[rkey] = r
}
sum += l * r
}
return sum
}
1.2.2. 代码解释
- 整个循环注意保持 循环不变量(loop invariant) ,这个在循环、递归等过程中是保证结果正确的重要性质。
- 如果我们只关注内部的递归过程,不难发现其本质上就是在构建一个二叉搜索树【LeetCode算法修炼指南】——108.将有序数组转换为二叉搜索树 ,
- 递归回溯的过程中,记录了当前数组左子树的可能数l以及右子树的可能数r,sun += l*r,l与r的结果总数是一个组合的可能性。如果觉得不太好理解那你可以假定现在左子树已经确定了,右边有n中可能,此时可能性是1*n,如果左子树有m种呢?这个乘法的过程称为组合(即笛卡尔积)。
1.3. 动态规划
动态规划解法参考官方题解,官方的解释非常清楚,需要一点数学的功底,但总体难说不难理解。
1.3.1. 数学公式推导
假设现在的根节点是3:
1.3.2. 代码片段
func numTrees3(n int) int {
G := make([]int, n+1) //[1...n]这个区间,再算上一个0,所以空间总数n+1
G[0] = 1
G[1] = 1
for i := 2; i <= n; i++ {
for j := 1; j <= i; j++ {
G[i] += G[j-1] * G[i-j]
}
}
return G[n]
}
1.4. 数学推导“卡塔兰数”
1.4.1. 公式:
1.4.2. 代码片段
func numTrees(n int) int {
var num = 1
for i := 1; i < n; i++ {
num = num * 2 * (2*i + 1) / (i + 2)
}
return num
}
来源:CSDN
作者:内脏裂了
链接:https://blog.csdn.net/weixin_42322309/article/details/104088413