动态规划:08不同的二叉搜索树

动态规划:08不同的二叉搜索树

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]

五部曲

  1. 确定dp数组含义:i个节点组成从1到i互不相同的二叉搜索树有dp[i]种

  2. 确定递归公式: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]的合法性

  3. dp数组初始化:dp[0] = 1

    空二叉树也是特殊的BST

  4. 遍历顺序:从前往后

    因为后面的值需要前面的值来推导

  5. 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];
    }
}
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n)

总结

这道题目虽然在力扣上标记是中等难度,但可以算是困难了!

首先这道题想到用动规的方法来解决,就不太好想,需要举例,画图,分析,才能找到递推的关系。

然后难点就是确定递推公式了,如果把递推公式想清楚了,遍历顺序和初始化,就是自然而然的事情了。

可以看出我依然还是用动规五部曲来进行分析,会把题目的方方面面都覆盖到!

用动规五部曲解斐波那契的时候,感觉简答题复杂化了。

但要知道,简单题是用来练习方法论的,并不能简单代码一甩,简单解释一下就完事了。

可能当时不理解,现在大家应该感受方法论的重要性了,加油

你可能感兴趣的:(算法刷题笔记,动态规划,算法)