题目:
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
首先二叉搜索树,肯定是左子树的节点小于根节点,然后右子树的节点大于根节点;
给定一个有序序列 1 ... n,为了根据序列构建一棵二叉搜索树。我们可以遍历每个数字 i,将该数字作为树根,1 ... (i-1) 序列将成为左子树,(i+1) ... n 序列将成为右子树。于是,我们可以递归地从子序列构建子树。
可以发现,左子树有i -1 个节点,从而这i -1个节点的排序方法,可以用个数i-1来表示,即G(i-1);因为这个个数肯定是有序的排列的,从而i-1个节点的左子树的排列方法就可以用长度i-1 来表示,即G(i-1);
从而;这就相当于这件事有两个步骤,步骤1有种解法,步骤2有种解法,从而完成这件事可以有种解法
则,n个节点的排列方法就为
从而长度为n的排列方法则为;
从而可以自底而上的计算每一种长度的解法;
(上面的这种解法,其实可以理解成动态规划)
code:
int numTrees(int n) {
vector<int> dp(n+1,0);
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;++i)
{
for(int j=1;j<=i;++j)
{
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}
当然也可以用递归来完成;
/* 我用lo,hi来表示左子树或右子树节点的区间;
* 从而挨个遍历其中的每一个,当作根节点,然后递归的去解左子树和右子树,然后相乘
* 但是这样,就会反复计算之前计算过的结果,因为对于后面的根节点,还是会计算之前计算过的区间
*/
class Solution {
public:
int digui(int lo,int hi)
{
int ans=0;
if(lo>=hi) return 1;
for(int i=lo;i<=hi;++i)
{
//表示左子树从lo开始到i-1这么多节点
int left=digui(lo,i-1);
//表示右子树从lo+1开始到hi这么多节点
int right=digui(i+1,hi);
ans+=left*right;
}
return ans;
}
int numTrees(int n) {
return digui(1,n);
}
};
时间复杂度:
递归的深度为O(n);每个根节点都要递归这么多的深度;
从而时间复杂度为
优化:
其实,这里从lo到hi这么多节点,其实和长度是一样的,
比如:计算[1,3]这个区间的排列方式的个数,它和计算[4,6]区间的排列方式的个数是一样的;
从而递归的版本,可以用长度来表示,但是还是避免不了重复计算,所以我可以用数组记录之前计算过的结果,也就是记忆化搜索
code:
class Solution {
public:
int digui(int n,vector<int> &dp)
{
if(dp[n]>0) return dp[n];
int ans=0;
if(n==0||n==1) return 1;
for(int i=1;i<=n;++i)
{
int left=digui(i-1,dp);
int right=digui(n-i,dp);
ans+=left*right;
}
dp[n]=ans;
return ans;
}
int numTrees(int n) {
vector<int> dp(n+1,-1);
return digui(n,dp);
}
};
时间复杂度:
由于记录之前算的长度,因此递归深度为O(n);但是每个节点只需要计算一次;
总的时间复杂度为O(n);
空间复杂度为O(n);