持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
题目描述
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入: n = 3
输出: 5
示例 2:
输入: n = 1
输出: 1
提示:
1 <= n <= 19
关于二叉搜索树
二叉搜索树是一个有序树,他有着几个特点
- 如果左子树不为空,那么左子树上所有结点的值均小于根结点的值
- 如果右子树不为空,那么右子树上所有结点的值均大于根结点的值
- 左右子树也分别为二叉搜索树
动态规划
如果整数1 ~ n中的 k 作为根节点值,则 1 ~ k-1 会去构建左子树,k+1 ~ n 会去构建右子树
当n为1,有一棵搜索树;
n=2,有两棵搜索树;
n为3的时候,就有5棵搜索树,这时,就可以找规律了:
1为头结点和3为头结点时,子树的布局其实是和n=2时一样的,可以分别对应上,同时,2为头结点时,子树布局和n=1时是一样的,这样我们就找到重叠子问题了。可以通过dp[1]和dp[2]推导出dp[3]
dp[3]=元素1为头结点的搜索树数量+元素2为头结点的搜索树数量+元素3为头结点的搜索树数量
- 元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
- 元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
- 元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
接下来我们就可以推出递推关系了dp[i] = dp[i] + dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量]
所以递推公式就是dp[i] = dp[i] + dp[j - 1] * dp[i - j]
这里的j-1 是以j为头结点左子树节点数量,i-j 是以j为头结点右子树节点数量
那么就可以写出这道题的做法
var numTrees = function(n) {
const dp = new Array(n + 1).fill(0);
dp[0] = 1;
dp[1] = 1;
for (let i = 2; i <= n; ++i) {
for (let j = 1; j <= i; ++j) {
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
};
js中的fill() 方法用于将一个固定值替换数组的元素。此处给数组中每个元素赋值0用来初始化数组
时间复杂度为O(n2)
空间复杂度为O(n)
动态规划算是比较常用的算法了,之前刷过好多题都有它的身影,像是计算岛屿数量、飞地数量等等,所以这一块一定要好好掌握。