原题地址:https://leetcode.com/problems/unique-binary-search-trees/
题目描述
Given n, how many structurally unique BST’s (binary search trees) that store values 1…n?
给出n,返回结构上不重复的二叉查找树的个数(每个二叉树都存储1,2,3,…,n)。
For example,
举例,
Given n = 3, there are a total of 5 unique BST’s.
给出n=3,共有5种各异的二叉查找树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
对于 n=3,返回 5 。
解题思路
这是一个动态规划问题。
由于我是先做的Leetcode 95(博客记录:Leetcode 95 Unique Binary Search Trees II),因此很自然得认为想到在Leetcode 95的基础上对程序进行修改即可轻松得到正确的代码。然而仅仅修改代码遇到了以下问题:
1. Memory Limit Exceeded 超过内存限制
2. Time Limit Exceeded 超过时间限制
为了解决上述方法,我主要采取以下方法来优化代码:
1. 特别注意对于内存的管理,对于已经不再使用的内存及时进行释放。
2. 有些情况下是无须重复计算的,如果重复计算的话就会有超时的可能。
对于第一点,这个没什么技巧, 对于无用的内存释放即可。
对于第二点,我们可以发现如下规律,对于两个不存在重复元素的数组arr1和arr2,当他们元素数量相同时,就算他们存储的元素不全都一样,他们可以构成的二叉查找树是一样多的。因此我们考虑使用一个数组numsTrees[n+1]来存储可以构成的异构二叉查找树的数目,这样只需要计算n+1次,其他情况下直接使用已经存储的结果即可。这是一种以空间换时间的方法。空间复杂度O(n),时间复杂度O(n)。
代码
/**
* 返回由n个数字可以构成的异构二叉查找树的数目
* Input n : 最大值(即1-n共n个数)
* Return : 异构的二叉查找树的数目
*/
int numTrees(int n) {
int numTreesFromArray(int *, int *, int);
// 如果n为0或1,直接返回1
if (n == 0 || n == 1)
return 1;
// 分配备选数组nums内存
int *nums = (int *)malloc(sizeof(int) * n);
// 分配numsTrees内存,numsTrees[i]代表用i个数可以构成的异构二叉查找树的数目
int *numsTrees = (int *)malloc(sizeof(int) * (n + 1));
// 初始化
int i;
for (i = 0; i < n; ++i) {
*(nums + i) = i + 1; // 初始化备选数组nums,nums中存储着1-n(有序)
*(numsTrees + i) = 0; // 初始化结果集numsTrees = {0}
}
*(numsTrees + n) = 0; // 初始化numsTrees[n] = 0
*numsTrees = 1; // 初始化numsTrees[0] = 1,空集只有一种构造方式
*(numsTrees + 1) = 1; // 初始化numsTrees[1] = 1,一个数字只有一种构造方式
// 获取使用nums可能异构的二叉查找树的数目
int count = numTreesFromArray(numsTrees, nums, n);
// 释放内存
free(nums);
free(numsTrees);
return count;
}
/**
* 返回由nums数组中的元素可以构造出的异构的二叉查找树的数目
* Input numsTrees : 存储由i个元素可以构成的异构二叉查找树的数目
* Input nums : 存储备选元素
* Input size : 备选元素个数
* Return : 异构的二叉查找树的数目
*/
int numTreesFromArray(int *numsTrees, int *nums, int size) {
// 如果已经计算过,直接返回
if (*(numsTrees + size))
return *(numsTrees + size);
int *littleNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点小的数
int *bigNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点大的数
int littleNumsCount, bigNumsCount, i, j, k, count = 0;
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可以构造出的异构的二叉查找树的数目
j = numTreesFromArray(numsTrees, littleNums, littleNumsCount);
// 返回由bigNums可以构造出的异构的二叉查找树的数目
k = numTreesFromArray(numsTrees, bigNums, bigNumsCount);
count += j * k; // 增加异构的二叉查找树的数目
}
// 释放内存
free(littleNums);
free(bigNums);
// 存储结果
*(numsTrees + size) = count;
return count;
}
运行情况
Status:Accept
Time:0ms
测试数据
0 : 1
1 : 1
2 : 2
3 : 5
4 : 14
5 : 42
6 : 132
7 : 429
8 : 1430
9 : 4862
10 : 16796
11 : 58786
12 : 208012
13 : 742900
14 : 2674440
15 : 9694845
16 : 35357670
17 : 129644790
18 : 477638700
19 : 1767263190
总结
运行时间竟然是0ms,之前的时候可是超时的啊,可见有些时候用空间来换时间还是非常值得的。代码优化的重要性也不言而喻。
// 个人学习记录,若有错误请指正,大神勿喷
// sfg1991@163.com
// 2015-05-26