96. 不同的二叉搜索树
首先很自然想到需要把n=1,和n=2画出来瞧一瞧,画出来以后我们发现3为头结点的时候,左子树两个节点的形状与n=2相同,好像有点关系,动规的是将问题拆分成若干个子问题,每一个状态由上一个状态推导而来,好像是那么回事,那么就用动规的思路试试
我们发现,树是BST,那么给我头节点的数值,其左右子树的数量就是确定的,想到这里就忍俊不禁了,这和动态规划:07整数拆分一样,借助j就可以了,在本题j是头结点,将不同的j情况作加和,那么就是答案
当3为头结点的时候,其左子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是一样的啊!
当2为头结点的时候,其左右子树都只有一个节点,布局是不是和n为1的时候只有一棵树的布局也是一样的啊!
那么我们要想到,如果确定了头结点,是否就可以由先前的dp数组值来推导现在的值
发现到这里,其实我们就找到了重叠子问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种方式。
思考到这里,这道题目就有眉目了。
dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
有2个元素的搜索树数量就是dp[2]。
有1个元素的搜索树数量就是dp[1]。
有0个元素的搜索树数量就是dp[0]。
所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
确定dp数组含义:i个节点组成从1到i互不相同的二叉搜索树有dp[i]种
确定递归公式:dp[i] += dp[j - 1] * dp[i - j] (j 从1至i)
以j为头节点,左子树就有j-1个节点,右子树有i-j个节点(因为是BST),将各种j为头结点的情况相加,就是总共的种类
我们发现,个数一定,只要是连着的数让其组成BST,其样子和种类是一样多的,也就是说1,2,3组成的BST和111,112,113组成的BST的种类样子是一样多的,即dp[3]的种类数和111,112,113组成的种类数一样,所以说明了dp[i - j]的合法性
dp数组初始化:dp[0] = 1
空二叉树也是特殊的BST
遍历顺序:从前往后
因为后面的值需要前面的值来推导
debug:打印dp数组
class Solution {
public int numTrees(int n) {
//初始化 dp 数组
int[] dp = new int[n + 1];
//初始化第0个节点,因为第1个节点带入递推公式是能够算出来的
dp[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
//对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
}
这道题目虽然在力扣上标记是中等难度,但可以算是困难了!
首先这道题想到用动规的方法来解决,就不太好想,需要举例,画图,分析,才能找到递推的关系。
然后难点就是确定递推公式了,如果把递推公式想清楚了,遍历顺序和初始化,就是自然而然的事情了。
可以看出我依然还是用动规五部曲来进行分析,会把题目的方方面面都覆盖到!
用动规五部曲解斐波那契的时候,感觉简答题复杂化了。
但要知道,简单题是用来练习方法论的,并不能简单代码一甩,简单解释一下就完事了。
可能当时不理解,现在大家应该感受方法论的重要性了,加油