题目描述1:
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
分析1:
如果n为5,[1,2,3,4,5],for循环依次以1、2、3、4、5为根节点,因为是二叉搜索树,所以根节点的左边要小于根,右边大于根,所以以1为根节点的二叉搜索树的个数就是1左边的组合个数乘上1右边组合的个数,那么n为5的二叉搜索树的个数就等于以以1,2,3,4,5为根节点的二叉搜索树的个数之和。这个题的关键点在于左右组合的个数,与左右组合的内容无关,只与左右组合的个数有关,也就是说我们可以使用动态规划,用一个数组dp[i]来保存个数为i时的组合个数。
定义dp数组
递推公式
初始化临界值
G[0] = 1;G[1] = 1;
代码1:
int numTrees(int n)
{
//动态规划
vector<int> G(n+1,0);
G[0] = 1;
G[1] = 1;
for (int i = 2; i <= n; ++i)
{
for (int j = 1; j <= i; ++j)
{
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
}
注意事项1:
这个题最关键的点就在于左/右孩子的组合个数只与节点个数有关,与节点的数据无关,例如:[1,2,3,4,5]与[4,5,6,8,9]构成的二叉搜索树的个数是一样的!
题目描述2:
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
示例:
分析2:
如果n=5,那么构成的所有二叉搜索树就是以1为根节点,1的左孩子是小于1的数的组合,1的右孩子是大于1的数的各种组合。因为题目要求得是返回各种二叉搜索树的组合,而不是个数组合的个数,所以这个题与上面那个题最大的不同就是,这个题的左右孩子组合成的二叉搜索树是与左右孩子的数据紧密关联的,所以我们需要一个数组来保存各种组合情况。我们需要在程序中使用两个容器,一个用来存储左孩子的各种组合,一个用来存储右孩子的各种组合,然后在存储完之后,我们还要把左右孩子的各种组合组装起来,存储到一个容器中,然后返回这个容器,运用递归完成该题。
代码2:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution
{
vector<TreeNode*> create(int left,int right)
{
if(left > right)
{
return {NULL};
}
vector<TreeNode*> dp;
for(int i = left;i <= right ;i++)
{
//TreeNode * root = new TreeNode(i); //如果root在这里定义结果就不正确,思考为什么
//因为第一个root插进去后,第二次root会改变,那个被插到容器中的root也会随之改变,就会出现重复
///收集i的左右孩子集合
vector<TreeNode*> left1 = create(left,i-1);
vector<TreeNode*> right1 = create(i+1,right);
//将i的左右孩子组合起来
for(int m=0;m<left1.size();m++)
{
for(int n=0;n<right1.size();n++)
{
TreeNode * root = new TreeNode(i);
root->left = left1[m];
root->right = right1[n];
dp.push_back(root);
}
}
}
return dp;
}
public:
vector<TreeNode*> generateTrees(int n)
{
if(n == 0) return {};
return create(1,n);
}
注意事项2:
这个两个题最大的区别就在于,第一个题只需要求二叉搜索树组合的个数,而第二个题需要输出各二叉搜索树组合,所以第二个题的左右孩子节点的数据不同结果就不同,所以我们要用到vector容器来保存左右孩子的各种情况。