题目简述
生成并返回所有由n个节点组成的节点值从1~n互不相同的二叉搜索树,可以按任意顺序返回答案
示例:
什么是二叉搜索树?(我看到题目的第一反应…)
简单解释就是:
左子树节点值<根节点值 且 右子树节点值>根节点值
并且左子树和右子树也是二叉搜索树
(听这个解释就有种浓浓的递归味)(二叉树题解皆可递归)
我的first通过答案(又臭又长,可跳过)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
//函数声明
struct TreeNode** T(int* array, int size, int* returnSize);
struct TreeNode** generateTrees(int n, int* returnSize){
int *array = (int *)malloc(sizeof(int) * n); //1~n的数组
int *returnNum = (int *)malloc(sizeof(int)); //返回值forest的大小
*returnNum = 0;
for(int i=0;i<n;i++){
array[i] = i+1;
}
struct TreeNode** forest = T(array,n,returnNum);
//题目给的参数列表不适合使用递归解法,因此我另外写了函数T求解二叉搜索树集
*returnSize = *returnNum;
free(array);
free(returnNum);
return forest;
}
struct TreeNode** T(int* array, int size, int* returnNum){
if(size == 0)
return NULL;
struct TreeNode** forest = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 10000);
//虚假的动态分配内存方法——内存消耗极大且不灵活
int num = 0;
int *leftnum = (int *)malloc(sizeof(int)); //左子树集大小
int *rightnum = (int *)malloc(sizeof(int)); //同上
for(int i=0;i<size;i++){
*leftnum = 0;
*rightnum = 0;
struct TreeNode** leftForest = T(array,i,leftnum); //递归求解左子树集
struct TreeNode** rightForest = T(array+i+1,size-i-1,rightnum); //同上
//由于子树集为NULL时长度为0,所以就需要繁琐的if-else判断
//↑太笨了
if(leftForest == NULL && rightForest == NULL){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = NULL;
forest[num++]->right = NULL;
}else if(leftForest == NULL && rightForest != NULL){
for(int j=0;j<*rightnum;j++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = NULL;
forest[num++]->right = rightForest[j];
}
}else if(leftForest != NULL && rightForest == NULL){
for(int j=0;j<*leftnum;j++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = leftForest[j];
forest[num++]->right = NULL;
}
}else{
for(int j=0;j<*leftnum;j++){
for(int k=0;k<*rightnum;k++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = leftForest[j];
forest[num++]->right = rightForest[k];
}
}
}
}
//好多忘记free的地方,内存泄露危机
*returnNum = num;
return forest;
}
官方优秀解法:
struct TreeNode** buildTree(int start, int end, int* returnSize) {
if (start > end) {
(*returnSize) = 1;
struct TreeNode** ret = malloc(sizeof(struct TreeNode*));
ret[0] = NULL;
return ret;
}
*returnSize = 0;
struct TreeNode** allTrees = malloc(0);
// 枚举可行根节点
for (int i = start; i <= end; i++) {
// 获得所有可行的左子树集合
int leftTreesSize;
struct TreeNode** leftTrees = buildTree(start, i - 1, &leftTreesSize);
// 获得所有可行的右子树集合
int rightTreesSize;
struct TreeNode** rightTrees = buildTree(i + 1, end, &rightTreesSize);
// 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for (int left = 0; left < leftTreesSize; left++) {
for (int right = 0; right < rightTreesSize; right++) {
struct TreeNode* currTree = malloc(sizeof(struct TreeNode));
currTree->val = i;
currTree->left = leftTrees[left];
currTree->right = rightTrees[right];
(*returnSize)++;
allTrees = realloc(allTrees, sizeof(struct TreeNode*) * (*returnSize));
allTrees[(*returnSize) - 1] = currTree;
}
}
free(rightTrees);
free(leftTrees);
}
return allTrees;
}
struct TreeNode** generateTrees(int n, int* returnSize) {
if (!n) {
(*returnSize) = 0;
return NULL;
}
return buildTree(1, n, returnSize);
}
直观打击:
二者对比——改进之处:
递归函数的参数列表设计
我选择传递数组,而官方解法是传递两个端点值start和end
前者需要数组初始化,时间受n影响,而后者不受n影响,明显后者更优
if-else的去除
我和官方都选择了递归解法,但是代码长度有很明显的区别,关键就在于递归获得左子树集和右子树集之后,官方直接两层for循环嵌套,枚举结果。而我则是做了if-else判断,有四种可能走向
直观对比:
//我的解法:
if(leftForest == NULL && rightForest == NULL){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = NULL;
forest[num++]->right = NULL;
}else if(leftForest == NULL && rightForest != NULL){
for(int j=0;j<*rightnum;j++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = NULL;
forest[num++]->right = rightForest[j];
}
}else if(leftForest != NULL && rightForest == NULL){
for(int j=0;j<*leftnum;j++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = leftForest[j];
forest[num++]->right = NULL;
}
}else{
for(int j=0;j<*leftnum;j++){
for(int k=0;k<*rightnum;k++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = leftForest[j];
forest[num++]->right = rightForest[k];
}
}
}
//官方解法:
for(int j=0;j<*leftnum;j++){
for(int k=0;k<*rightnum;k++){
forest[num] = (struct TreeNode*)malloc(sizeof(struct TreeNode));
forest[num]->val = array[i];
forest[num]->left = leftForest[j];
forest[num++]->right = rightForest[k];
}
}
官方解法就是我的if-else中的第四种情况,泪奔了
为什么会有这种区别?
我使用if-else结构进行判断,是因为存在子树集为空,长度为0的情况,这种情况对于for循环来说不满足循环条件
而官方的解决方案很简单:创建一个长度为1/存在一棵树的forest,而这棵树为NULL(这样就可以参与for循环了)
动态分配forest内存(malloc和realloc函数使用)
我对于动态分配内存的认知仅限于malloc函数
malloc函数对于空间大小已知的情况非常适用,例如我要创建一个大小为**n(变量)**的int类型数组
int *p = (int *)malloc(sizeof(int) * n);
但是对于这道题目,我并不知道解的个数,只能蒙一个比较大的数字,确保空间足够使用(一开始猜了100,不够——>1000——>10000)
但是这样对于n的值较小的情况就会造成空间大量浪费,完全不算动态分配空间了。
官方解法给我指了条明路——realloc函数,使得空间在需要的时候动态+1+1+1…
来看具体使用过程:
for (int left = 0; left < leftTreesSize; left++) {
for (int right = 0; right < rightTreesSize; right++) {
//先创建一棵临时树
struct TreeNode* currTree = malloc(sizeof(struct TreeNode));
currTree->val = i;
currTree->left = leftTrees[left];
currTree->right = rightTrees[right];
(*returnSize)++;
//动态增加forest大小,增加一棵树的大小
//space = realloc(space, sizeof(type)*newSize);
//realloc函数可以用于动态增大或者减小空间
allTrees = realloc(allTrees, sizeof(struct TreeNode*) * (*returnSize));
allTrees[(*returnSize) - 1] = currTree;
}
}
增加对左子树集和右子树集的free操作
这个纯粹是我忘记free了,低级问题
free(rightTrees);
free(leftTrees);
本人菜鸡一只
如有错误,欢迎指正,卑微鞠躬