这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
不同的二叉搜索树(题号96)
题目
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:

输入:n = 3
输出:5
示例 2:
输入:n = 1
输出:1
提示:
1 <= n <= 19
链接
解释
这题啊,这题是小斐波那契数列。
说实话,原理还是十分类似的,想想斐波那契数列是怎么求解的?
function fib(n) {
const arr = [0, 1]
for (let i = 2; i <= n; i++) {
arr[i] = arr[i - 1] + arr[i - 2]
}
return arr[n]
}
这里的求解方法是缓存之前的值,根据旧的值推导出新的值,这题也是同样的想法。
求1和2就不说了,想想3是什么情况,因为是二叉搜索树,所以从上到下的顺序只有一种,所以只要考虑左右两棵树的情况即可。
如果总共有3个节点,肯定有一个是头节点,这里不表。
那么左右子树有多少种可能呢?
| 左子树节点数 | 右子树节点数 | 可能性 |
|---|---|---|
| 0 | 2 | 2 |
| 1 | 1 | 1 |
| 2 | 0 | 2 |
这么一看是不是就清楚了,如果觉得不清楚咱们再看看4个节点的情况:
| 左子树节点数 | 右子树节点数 | 可能性 |
|---|---|---|
| 0 | 3 | 5 |
| 1 | 2 | 2 |
| 2 | 1 | 2 |
| 3 | 0 | 5 |
对吧,可能性的多少和节点的个数是有一定规律的,规律也很简单,就是用左右子树分配节点的个数,那么在拿到节点个数后,首先需要去掉一个头节点,之后依次分配给左右子树,取它们可能性的乘积即可。
不过这里需要注意一点,在0节点的时候,需要按照可能性为1来处理,要不然0乘以任何数都为0,这不直接GG了么。
那么现在,只需要一个时间复杂度为O(nlogn)的算法就可以了,上代码!
自己的答案(双循环)
function numTrees(n) {
const arr = [0, 1, 2]
for (let i = 0; i <= n; i++) {
let count = 0
for (let j = 0; j < i; j++) {
if (!j || j === i - 1) {
count += Math.max(arr[j], arr[i - 1])
} else {
count += arr[j] * arr[i - 1 - j]
}
}
if (!arr[i]) arr[i] = count
}
return arr[n]
};
笔者的方法比官方复杂了一些,主要是考虑到了n为0的情况,其实完全不需要考虑,因为官方已经给定了条件:
1 <= n <= 19
这是完全不需要担心的事情,只需要把数组种的0改成1就会简单很多。
更好的方法(双循环)
官方答案看起来就清爽很多了:
function numTrees(n) {
const arr = new Array(n + 1).fill(0)
arr[0] = 1
arr[1] = 1
for (let i = 2; i <= n; i++) {
for (let j = 1; j <= i; j++) {
arr[i] += arr[j - 1] * arr[i- j]
}
}
return arr[n]
}
因为去掉了n为0的情况,而且第一层循环是从2开始的,所以很多特殊情况都不需要考虑了,而且在一开始给定了数组长度,这样就不用像笔者一样需要通过count变量来累计可能性了,直接在数组上进行修改即可,方便快捷。
更好的方法(数学)
这一部分就不解释了,据说是 卡塔兰数 ,笔者也没有研究。
var numTrees = function(n) {
let C = 1;
for (let i = 0; i < n; ++i) {
C = C * 2 * (2 * i + 1) / (i + 2);
}
return C;
};
奇思妙想
这题有一个答案看起来很有趣,经典的面向测试用例编程,哈哈哈。
class Solution {
public:
int numTrees(int n) {
switch(n){
case 1: return 1;
case 2: return 2;
case 3: return 5;
case 4: return 14;
case 5: return 42;
case 6: return 132;
case 7: return 429;
case 8: return 1430;
case 9: return 4862;
case 10: return 16796;
case 11: return 58786;
case 12: return 208012;
case 13: return 742900;
case 14: return 2674440;
case 15: return 9694845;
case 16: return 35357670;
case 17: return 129644790;
case 18: return 477638700;
case 19: return 1767263190;
default: return 0;
}
}
};
非常切合题意, 可真是个小机灵鬼儿 。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇