原题地址: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]。我们在构造时,仅考虑当前取什么值作为根节点即可,然后用小于这个值的数组构造左子树,大于这个值的数组构造右子树。
算法描述
- 初始化数组nums[1, 2, 3, …, n]
- 循环选取待选数组中的一个数字i,使用i作为根节点的值
- 将数组中小于i的数构成littleNums,并用该数组构造出所有可能的左子树leftTrees
- 将数组中大于i的数构成bifNuma,并用该数组构造出所有可能的右子树rightTrees
- 使用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