动态规划第二弹二叉树全排列组合so easy |Java 刷题打卡

1,494 阅读4分钟

本文正在参加「Java主题月 - Java 刷题打卡」,详情查看 活动链接

一、题目描述

不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

image-20210521165238029

二、思路分析

  • 本题依旧是动态规划。初看此题时容易被题目吸引注意力误认为是二叉树考点。实际上只是借助了二叉树的外壳内在是动态规划。

为什么是动态规划

  • 为什么我说考察的是动态规划呢?因为题目中并没有要求我们对二叉树进行遍历修改或者使用到二叉树的特性。题目中仅仅是使用了二叉树是两个分支的特性
  • 我们将题目可以做如下的理解。在N 个节点摆出二叉树求有多少种摆法 。
  • 二叉树只会有两颗树枝。当我们知道N-1个节点构成二叉树的个数时,N个节点就是在N-1的二倍基础上进行平衡等操作。所以N和N-1是有关联的
  • 所以笔者才说这里是动态规划的考点

正式分析

  • 既然是动态规划,那么我们还是原来的三板斧。转换方程--》初始值--》计算

转换方程

  • 二叉树是左右两颗子树,所以我们设当前节点个数为m 。那么m-1个节点组成的二叉树个数则为F(m-1) 。 而由m-1个节点组成的二叉树可以在第m个节点的左边,也可以在他的右边。

image-20210521170538370

  • 所以F(m)至少包含2*F(m-1)。 但是m以下的节点数不仅仅只有m-1 。 还有m-2 。
  • 此时我们将m-1进行分堆,比如分成k 和m-1-k 两堆 。 而分别有k个节点可以组成二叉树的可能我们叫做F(k) ,由(m-1-k)组成的二叉树可能我们叫做F(m-1-k)。
  • 而k和m-1-k两堆分别对应m节点作为他的左子树和右子树 。 这里有的人可能认为左右可以对调应该是两倍。这里我们的k是从1到m-2的。所以这里不需要两倍

image-20210521171023737

  • 加入我们m节点是5 。那么我们的m=5节点左子树会出现如下情况 F(1),F(2),F(3) ,而右子树分别对应的是F(3),F(2),F(1) 。

  • 所以F(m)除了包含2*F(m-1)外,还有F(k)*F(m-1-k)所有的和。这里的k范围1<=k<=m-1-1

  • 最终我们可以确定如下转换方程式

f(x)=2f(x1)+f(1)f(x2)+f(2)f(x3)++f(x2)f(1)f(x)=2 \cdot f(x-1)+f(1)\cdot f(x-2)+f(2) \cdot f(x-3)+\cdots+f(x-2) \cdot f(1)

初始值

  • 二叉树如果没有节点则没有二叉树,有一个节点则二叉树就是1 。 所以F(0)=0 ; F(1) = 1

有小到大计算

  • 有了初始值,而且转换方程没有条件判断。所以这道题只要方程式整理清楚,剩下的就会变的非常简单

三、AC 代码

  • 基于上述的方程式和初始值我们很容易进行推导,但是推导过程中会用到之前的所有的情况。所以这里我们需要新建一个数组来维护每个状态的数量统计

int[] arr = new int[n];
  • 为了我们方便理解,这里我们将数组创建为n+1长度。arr[0] 我们不适用。完全是为了方便我们的理解才这样做的。

  • 然后在每个下游二叉树节点需要从1到i-1 分别计算左右两边的可能的乘机就可以计算出i节点的数量了。

public int numTrees(int n) {
    if (n == 1) {
        return 1;
    }
    int[] arr = new int[n+1];
    arr[1] = 1;
    for (int i = 2; i <= n; i++) {
        int subTotal = 0;
        for (int j = 1; j < i; j++) {
            subTotal += arr[j] * arr[i-1 - j];
        }
        arr[i] = 2 * arr[i - 1] + subTotal;
    }
    return arr[n];
}

image-20210521172430602

四、总结

  • 表面看起来是二叉树,但是不要被他迷惑
  • 动态规划三步骤牢记,就可以从容应对动归的问题。

点赞、收藏、评论是你对我的指出