Leetcode 95 Unique Binary Search Trees II

原题地址:https://leetcode.com/problems/unique-binary-search-trees-ii/

题目描述

Given n, generate all structurally unique BST’s (binary search trees) that store values 1…n.
给出n,生成所有存储1-n共n个数的结构上不重复的二叉查找树。

For example,
举例,

Given n = 3, your program should return all 5 unique BST’s shown below.
给出n=3,你的程序需要返回如下5种各异的二叉查找树:

 1         3     3      2      1
  \       /     /      / \      \
   3     2     1      1   3      2
  /     /       \                 \
 2     1         2                 3

解题思路

这是一个动态规划问题。

构造一棵树大概分为三点,构造根节点+构造根节点的左子树+构造根节点的右子树。

对于二叉查找树中的节点来说,其左子树上的节点值一定小于根节点的值,右子树上的节点值一定大于根节点的值。因此当我们确定了一个根节点的值后,只需要将剩余数字中比它小的都用来构造其左子树,剩余数字中较大的都用来构造其右子树即可。

针对于这个问题,我们不妨将问题一般为如下描述:

给出一个数组,返回这个数组所能构成的所有异构的二叉查找树

我们的题目是上面描述的一种特殊情况,即数组为[1, 2, 3, …, n]。我们在构造时,仅考虑当前取什么值作为根节点即可,然后用小于这个值的数组构造左子树,大于这个值的数组构造右子树。

算法描述

  1. 初始化数组nums[1, 2, 3, …, n]
  2. 循环选取待选数组中的一个数字i,使用i作为根节点的值
  3. 将数组中小于i的数构成littleNums,并用该数组构造出所有可能的左子树leftTrees
  4. 将数组中大于i的数构成bifNuma,并用该数组构造出所有可能的右子树rightTrees
  5. 使用i作为根节点,leftTrees和rightTrees的组合分别作为其左右子树构成多棵树

代码一 C++版

/**
 * 二叉树节点的定义
 * Definition for a binary tree node.
 */
 struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 };
class Solution {
public:
    /**
     * 生成存储着1-n的所有异构的二叉查找树
     * Input n : 最大值(1-n)
     * Return : 异构的二叉查找树的集合
     */
    vector<TreeNode *> generateTrees(int n) {
        // 初始化备选数组nums,nums中存储着1-n(有序)
        vector<int> nums(n);
        for (int i = 0; i < n; ++i)
            nums[i] = i + 1;

        // 使用nums构造所有可能的异构的二叉查找树
        return generateTreesUseArray(nums);
    }

    /**
     * 使用nums数组中的元素来构造所有异构的二叉查找树
     * Input nums : 存储备选元素
     * Return : 异构的二叉查找树的集合
     */
    vector<TreeNode *> generateTreesUseArray(vector<int> nums) {
        vector<TreeNode *> ret;

        // 如果数组为空,返回NULL
        if (nums.size() == 0) {
            ret.push_back(NULL);
            return ret;
        }

        // 如果数组中只有一个元素,返回一个叶子节点(可以没有这种特殊情况的判定)
        // 如果不加这个特殊情况的判定,则程序会多两个nums为空的递归调用
        if (nums.size() == 1) {
            TreeNode *tmp = new TreeNode(nums[0]);
            ret.push_back(tmp);
            return ret;
        }

        // 遍历nums中的元素,将其作为根节点的值
        // (nums中每一个元素都可以作为根节点)
        for (int i = 0; i < nums.size(); ++i) {
            vector<int> litteNums; // 用来存储比根节点小的数
            vector<int> bigNums;   // 用来存储比根节点大的数

            // 将nums分成比根节点小的和比根节点大的两个数组
            int j;
            for (j = 0; j < nums.size(); ++j) {
                if (nums[j] >= nums[i])
                    break;
                litteNums.push_back(nums[j]);
            }
            if (nums[j] == nums[i]) ++j;
            for (; j < nums.size(); ++j)
                bigNums.push_back(nums[j]);

            // 使用littleNums构造所有可能的左子树(左子树也都是二叉查找数)
            vector<TreeNode *> leftTrees = generateTreesUseArray(litteNums);
            // 使用bigNums构造所有可能的右子树(右子树也都是二叉查找数)
            vector<TreeNode *> rightTrees = generateTreesUseArray(bigNums);

            // 遍历左子树和右子树的组合,作为根节点的左右节点,添加到结果集中
            for (int k = 0; k < leftTrees.size(); ++k)
                for (int l = 0; l < rightTrees.size(); ++l) {
                    TreeNode *tmp = new TreeNode(nums[i]); // 根节点
                    tmp->left = leftTrees[k];  // 左节点
                    tmp->right = rightTrees[l];// 右节点
                    ret.push_back(tmp); // 添加到结果集中
                }
        }

        return ret;
    }
};

运行情况

Status:Accept
Time:32ms

代码二 C版

/**
 * 生成存储着1-n的所有异构的二叉查找树
 * Input n : 最大值(1-n)
 * Input returnSize : 用于存储返回结果集的大小
 * Return : 异构的二叉查找树的集合 
 */
struct TreeNode **generateTrees(int n, int* returnSize) {
    struct TreeNode **generateTreesFromArray(int *, int, int *);

    // 初始化备选数组nums,nums中存储着1-n(有序)
    int *nums = (int *)malloc(sizeof(int) * n);
    int i;
    for (i = 0; i < n; ++i)
        *(nums + i) = i + 1;

    // 使用nums构造所有可能的异构的二叉查找树
    return generateTreesFromArray(nums, n, returnSize);
}

/**
 * 使用nums数组中的元素来构造所有异构的二叉查找树
 * Input nums : 存储备选元素
 * Input size : 备选元素个数
 * Input returnSize : 用于存储返回结果集的大小
 * Return : 异构的二叉查找树的集合
 */
struct TreeNode **generateTreesFromArray(int *nums, int size, int *returnSize) {
    struct TreeNode **ret;

    // 如果数组为空,返回NULL
    if (size == 0) {
        ret = (struct TreeNode **)malloc(sizeof(struct TreeNode *));
        *ret = NULL;
        *returnSize = 1; // NULL也是一种树的构成情况
        return ret;
    }

    // 如果数组中只有一个元素,返回一个叶子节点(可以没有这种特殊情况的判定)
    // 如果不加这个特殊情况的判定,则程序会多两个nums为空的递归调用
    if (size == 1) {
        ret = (struct TreeNode **)malloc(sizeof(struct TreeNode *));
        struct TreeNode *tmp = (struct TreeNode *)malloc(sizeof(struct TreeNode));
        tmp->val = *nums;
        tmp->left = NULL;
        tmp->right = NULL;
        *ret = tmp;
        *returnSize = 1; // 叶子节点构造树只有一种
        return ret;
    }

    // 遍历nums中的元素,将其作为根节点的值
    // (nums中每一个元素都可以作为根节点)
    *returnSize = 0; // 结果集大小初始化为0
    int i, j, k, total = 100; // total为结果集预留总大小,不够时再分配
    ret = (struct TreeNode **)malloc(sizeof(struct TreeNode *) * total);
    int littleNumsCount, bigNumsCount, leftTreesCount, rightTreesCount, old;
    int *littleNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点小的数
    int *bigNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点大的数

    for (i = 0; i < size; ++i) { // 遍历nums中的元素,将其作为根节点的值        
        littleNumsCount = 0; // 初始化littleNums的数量
        bigNumsCount = 0; // 初始化bigNums的数量
        // 将nums分成比根节点小的和比根节点大的两个数组
        for (j = 0; j < size; ++j) {
            if (*(nums + j) >= *(nums + i))
                break;
            *(littleNums + littleNumsCount++) = *(nums + j);
        }
        if (*(nums + j) == *(nums + i))
            ++j;
        for (; j < size; ++j)
            *(bigNums + bigNumsCount++) = *(nums + j);

        // 使用littleNums构造所有可能的左子树(左子树也都是二叉查找数)
        struct TreeNode **leftTrees = generateTreesFromArray(littleNums, littleNumsCount, &leftTreesCount);
        // 使用bigNums构造所有可能的右子树(右子树也都是二叉查找数)
        struct TreeNode **rightTrees = generateTreesFromArray(bigNums, bigNumsCount, &rightTreesCount);

        // 检查结果集的空间是否够用
        old = total;
        while (total < *returnSize + leftTreesCount * rightTreesCount) {
            total *= 2;
        }
        // 如果不够用,则重新分配结果集空间
        if (old < total)
            ret = (struct TreeNode **)realloc(ret, sizeof(struct TreeNode *) * total);

        // 遍历左子树和右子树的组合,作为根节点的左右节点,添加到结果集中
        for (j = 0; j < leftTreesCount; ++j)
            for (k = 0; k < rightTreesCount; ++k) {
                struct TreeNode *tmp = (struct TreeNode *)malloc(sizeof(struct TreeNode)); // 根节点
                tmp->val = *(nums + i);        // 根节点的值
                tmp->left = *(leftTrees + j);  // 左节点
                tmp->right = *(rightTrees + k);// 右节点
                *(ret + *returnSize) = tmp;    // 添加到结果集中
                ++*returnSize;                 // 更新结果集大小
            }
    }

    // 释放内存 
    free(littleNums);
    free(bigNums);

    return ret;
}

运行情况

Status:Accept
Time:8ms

总结

从代码上来说,对于nums特殊情况的判断可以只有nums是否为空这一种特殊情况的判断。但是那样的话对于只有一个元素的集合来说,会增加两次递归调用。经测试,在去掉对nums只有一个元素的情况的判断时,代码在leetcode上运行时间同样为8ms,可能是在测试数据量小的情况下没有太大差别。

从运行结果上来说,C++和C版代码尽管结构一样,运行时间确有不小差别。

另外,需要注意的是,当使用C++时,新建一个TreeNode变量的方法有两种:

TreeNode tmp(1);
TreeNode *permnant = new TreeNode(1);

上述两种方法中,第一种创建了一个临时TreeNode变量,第二种创建了一个TreeNode并创建了一个指向这个TreeNode的指针。我们应该注意在程序中应该使用第二种。这是因为第一种创建的是一个在被调用函数作用域内的临时变量(在栈中),当程序调用结束时就被弹出栈并释放了,那样的话我们构造的二叉树就没有了;而我们使用第二种方式是在堆上动态分配的内存,即使函数调用结束后,只要我们不使用delete来释放内存,其内存储的值会一直都在。

对于上述问题,在C语言中相应的使用malloc来动态分配内存。


// 个人学习记录,若有错误请指正,大神勿喷
// sfg1991@163.com
// 2015-05-13

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值