给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入:n = 3
输出:5
示例 2:
输入:n = 1
输出:1
提示:
1 <= n <= 19
二叉查找树(又:二叉搜索树,二叉排序树)
它或者是一棵空树,
或者是具有下列性质的二叉树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
本题思路解释
由于 1,2…n 这个数列是递增的,所以我们从任意一个位置“提起”这课树,都满足二叉搜索树的这个条件:左边儿子数小于爸爸数,右边儿子数大于爸爸数
从 1,2,…n 数列构建搜索树,实际上只是一个不断细分的过程
例如,我要用 [1,2,3,4,5,6] 构建
首先,提起 “2” 作为树根,[1]为左子树,[3,4,5,6] 为右子树
现在就变成了一个更小的问题:如何用 [3,4,5,6] 构建搜索树?
比如,我们可以提起 “5” 作为树根,[3,4] 是左子树,[6] 是右子树
现在就变成了一个更更小的问题:如何用 [3,4] 构建搜索树?
那么这里就可以提起 “3” 作为树根,[4] 是右子树
或 “4” 作为树根,[3] 是左子树
可见 n=6 时的问题是可以不断拆分成更小的问题的
假设 f(n)= 我们有 n 个数字时可以构建几种搜索树
我们可以很容易得知几个简单情况 f(0) = 1, f(1) = 1, f(2) = 2
(注:这里的 f(0) 可以理解为 =1 也可以理解为 =0,这个不重要,我们这里理解为 =1,即没有数字时只有一种情况,就是空的情况)
那 n=3 时呢?
我们来看 [1,2,3]
如果提起 1 作为树根,左边有f(0)种情况,右边 f(2) 种情况,左右搭配一共有 f(0)*f(2) 种情况
如果提起 2 作为树根,左边有f(1)种情况,右边 f(1) 种情况,左右搭配一共有 f(1)*f(1) 种情况
如果提起 3 作为树根,左边有f(2)种情况,右边 f(0) 种情况,左右搭配一共有 f(2)*f(0) 种情况
容易得知 f(3) = f(0)*f(2) + f(1)*f(1) + f(2)*f(0)
同理,
f(4)f(4) = f(0)f(0)*f(3)f(3) + f(1)f(1)*f(2)f(2) + f(2)f(2)*f(1)f(1) + f4(3)f4(3)*f(0)f(0)
f(5)f(5) = f(0)f(0)*f(4)f(4) + f(1)f(1)*f(3)f(3) + f(2)f(2)*f(2)f(2) + f(3)f(3)*f(1)f(1) + f(4)f(4)*f(0)f(0)
…
发现了咩?
对于每一个 n,其式子都是有规律的
每一项两个 f() 的数字加起来都等于 n-1
既然我们已知 f(0)=1, f(1)=1
那么就可以先算出 f(2),再算出 f(3),然后 f(4) 也可以算了…
计算过程中可以把这些存起来,方便随时使用
最后得到的 f(n) 就是我们需要的解了
代码实现(记):
class Solution:
def numTrees(self, n: int) -> int:
store = [1,1] #f(0),f(1)
if n <= 1:
return store[n]
for m in range(2,n+1):
s = m-1
count = 0
for i in range(m):
count += store[i]*store[s-i]
store.append(count)
return store[n]
动态规划形式:
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1)
dp[0], dp[1] = 1, 1
for i in range(2, n + 1):
for j in range(1, i + 1):
dp[i] += dp[j - 1] * dp[i - j]
return dp[-1]