LeetCode-95 不同的二叉搜索树
题目
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树
思路
从递归入手思考,对1~n中的每一个数 i,都可以当做树的根节点,此时以 i 为根节点的子树个数 f(n)可以表示为 f(n) = f(i-1) * f(n-i),对于 f(i-1) 和 f(n-i)此公式依然适用,因而可以递归的求解。但指数级的时间复杂度显然不是我们想要的。考虑采用动态规划对递归算法进行优化。
我们先来看看递归算法的缺点在哪里:
为了方便计算,我们让f(0) = 1,另外显然的有f(1) = 1
以计算f(3)为例:
f(3) = f(0) * f(2) + f(1) * f(1) + f(2) * f(0)
= f(0) * [f(0) * f(1) + f(1) * f(0)] + f(1) * f(1) + [f(0) * f(1) + f(1) * f(0)] * f(0)
看到问题了吗。在计算f(3) 的时候我们重复计算了多次f(2),而这是不必要的。
如果我们能通过一个数组记录下所有已经算过的节点,就可以减少计算次数从而达到降低时间复杂度的目的,这便是动态规划。
状态转换方程
经过上述分析可以得出:
f
(
x
)
=
∑
i
=
1
N
[
f
(
i
−
1
)
∗
f
(
n
−
i
)
]
f(x) = \begin{matrix} \sum_{i=1}^N [f(i-1) * f(n-i)] \end{matrix}
f(x)=∑i=1N[f(i−1)∗f(n−i)]
其实就是递归的方程,只不过把算过的节点记下来,用的时候直接带就完了
C语言实现
代码很简单
//状态方程 fn = Σi(0~n-1)(fi * f(n-i-1))
int numTrees(int n){
//初始化dp数组
int dp[n+1];
memset(dp, 0, sizeof(dp));
//注意这里不能用dp[n+1] = {0}
//初始化前两者
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n+1; i++){
for(int j = 0; j < i; j++){
dp[i] += dp[j] * dp[i-j-1];
}
}
return dp[n];
}
时间复杂度O(n^2) :二重循环
空间复杂度O(n) : 额外维护数组的开销
卡特兰数
实际上二叉搜索树的个数问题,这样的数列被称为卡特兰数(人家早就研究透了),拥有一个通项公式:
C
(
0
)
=
1
C(0) = 1
C(0)=1
C
(
n
+
1
)
=
2
(
2
n
+
1
)
n
+
2
C
(
n
)
C(n+1) = \frac{2(2n+1)}{n+2}C(n)
C(n+1)=n+22(2n+1)C(n)
卡特兰数又称卡塔兰数,卡特兰数是组合数学中一个常出现在各种计数问题中的数列。以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)的名字来命名。
比较常见的应用有:
1.矩阵连乘的括号优化方案
2.出栈次序
一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?
3.凸多边形三角划分
在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。任务是键盘上输入凸多边形的边数n,求不同划分的方案数f(n)
奇怪的数学知识又增加了!!!