题意:
给一个n,问能存储从1到n 的二叉排序树有多少个。
分析:
我们对前几个比较小的树,举例子画图分析。我们一定要搞清楚,为什么3个结点就是5个树,4个结点就是14个树。多画了几个树之后,很明显自然地就想到动态规划,我们能不能用不同的结点做根结点,然后左子树的值乘上右子树,任由子树变换。
这就是我们的编程思维:
对于一个树:
*
*(left) *(right)
比如一个9个结点的,现在左子树有3个结点,右子树有5个结点,再加一个根结点,所以f(9) = f(3) * f(5) (当第四个结点做根节点的时候),我们只需要将每个结点做根的结果求和即可。(而不需要管左右子树具体是什么情况),后面就比较简单了,不做分析。
小结:
我一直认为智力是个伪命题,只有针对某特定领域问题的一定思维模式下的思考效率。而编程就是要培养一种编程思维模式,我们不要像解数学题一样去找n与f(n)的关系,也不是出于找递推去找规律,本质算法就不是找规律,而是找解决办法,(不是找值是多少,而是找怎么求得这个值的)我们应该从一般情况出发,从动态变化出发,我们举个例子画个图,或者去想一个比较大的n,它的f(n)是怎么得出来的?左子树好复杂,右子树好复杂,但是子树的变化情况我们是已知的(动态存储,即动态规划)所以左子树的值乘上右子树的值,就是那颗树的值,再考虑遍历所有结点做根的情况即可。
得出解法的必然性:我们一定要去想子树啊,动态的想,不要去管子树具体啥样。而去想树和子树的联系。
public class Solution {
public int numTrees(int n) {
if(n < 3){
return n;
}
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
dp[2] = 2;
int tmp = 0;
int j = 1;
for(int i=3; i<=n; i++){
j = 1;
while(j <= i/2){
tmp += 2*dp[i-j]*dp[j-1];
j++;
}
if(i%2 != 0)
tmp += dp[j-1]*dp[j-1];
dp[i] = tmp;
tmp = 0;
}
return dp[n];
}
}