96. Unique Binary Search Trees
Given n, how many structurally unique BST's (binary search trees) that store values 1 ... n?
Example:
Input: 3
Output: 5
Explanation:
Given n = 3, there are a total of 5 unique BST's:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
题目链接:https://leetcode.com/problems/unique-binary-search-trees/
解题抓瞎思路
对于这种递增的题目,很容易想到DP,关键在于DP表达式是怎么样的。
一般我做DP的思路是,从n=1开始,列出前几种n较小情况的解,再总结规律。
在本题中,
n=1,solution=1
n=2,solution=2
而题中给出了
n=3,solution=5
我尝试写出n=4的情况,发现在树结构上从n变为n+1时,新节点插入的方法:
1)向n下所有树向外延伸一条边插入,作为根节点或叶子节点。即对每个树结构有2种插入方式。
2)向n下所有树中间插入,作为非根节点、非叶子节点。这里的计算,因为BST的子数还是BST,所以可以根据前面已经算出的节点数对应的BST个数通过排列组合得到。
举个例子:
这是我直观感受总结的规律,但实际上还是复杂了一步,1)可合并入2)中。
接下来是正确的打开方式
正确解法
实际上正确的思路只需要2),1)是完全可以并入2)中的。
太久没做题了,忘记DP正确的打开方式,本题是将问题递归到子问题上去,这题就直接对子树出现的情况进行排列组合:
1)任意数字都可作为根节点。
2)根节点的左右子树各自是一个本题的子问题,两个子问题之间是组合关系(相乘关系)。
有了以上两点核心思路,写出表达式,这题就解完了。
dp[n] = dp[0] * dp[n-1] + dp[1] * dp[n-2] + ...
= sum(dp[i] * dp[n-i-1]) for 0<=i<=n-1
法一:循环计算
写循环实现。
代码:
class Solution {
public:
int numTrees(int n) {
int dp[n+1] = {0};
dp[0] = 1;
for (int i = 1; i<=n; ++i){
for(int j = 0; j <= i-1; ++j){
dp[i] += dp[j] * dp[i-j-1];
}
}
return dp[n];
}
};
法二:算出通项公式
进一步算出由表达式推倒出来的通项公式。
这个方法是查资料得到的,此题的计算结果正式卡塔兰数,由卡塔兰数的推倒公式得到通项式为C(2n, n)/(n+1),表示2n个数中任意取n个,再除以(n+1)。
为了防止相乘时整型移除,需要使用long。
代码:
class Solution {
public:
int numTrees(int n) {
long res = 1;
for (int i = n + 1; i <= 2 * n; ++i) {
res = res * i / (i - n);
}
return res / (n + 1);
}
};
延伸学习
卡塔兰数
卡塔兰数和斐波那契数列一样,就是一种特殊的数列,具有一定的几何意义,常与实际问题相关,因此就有人去研究其更多性质。
如何理解卡塔兰数:
卡塔兰数简单印象:https://blog.csdn.net/wu_tongtong/article/details/78161211
维基百科更多性质,开始看不懂了:https://zh.wikipedia.org/zh-hans/%E5%8D%A1%E5%A1%94%E5%85%B0%E6%95%B0
todo
卡塔兰数的四种问题