Leetcode95.不同的二叉搜索树——如何真正实现动态分配内存

题目简述

生成并返回所有由n个节点组成的节点值从1~n互不相同的二叉搜索树,可以按任意顺序返回答案
示例:
n=3的情况

什么是二叉搜索树?(我看到题目的第一反应…)

简单解释就是:
左子树节点值<根节点值 且 右子树节点值>根节点值
并且左子树和右子树也是二叉搜索树
(听这个解释就有种浓浓的递归味)(二叉树题解皆可递归)

我的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);

本人菜鸡一只
如有错误,欢迎指正,卑微鞠躬

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值