LeetCode每日一题--96. 不同的二叉搜索树(Java)

181 阅读2分钟

DailyChallenge

96. 不同的二叉搜索树

20200715

难度:中等

题目描述

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

链接:<https://leetcode-cn.com/problems/unique-binary-search-tree

Solution

  1. 方法一:动态规划

思路

给定一个有序序列 ,为了构建出一棵二叉搜索树,我们可以遍历每个数字 ,将该数字作为树根,将 序列作为左子树,将 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。

在上述构建的过程中,由于根的值不同,因此我们能保证每棵二叉搜索树是唯一的。

由此可见,原问题可以分解成规模较小的两个子问题,且子问题的解可以复用。因此,我们可以想到使用动态规划来求解本题。

算法

题目要求是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:

: 长度为 n 的序列能构成的不同二叉搜索树的个数。

: 以 i 为根、序列长度为 n 的不同二叉搜索树个数

可见, 是我们求解需要的函数。

稍后我们将看到, 可以从 得到,而 又会递归地依赖于

首先,根据上一节中的思路,不同的二叉搜索树的总数 ,是对遍历所有 之和。换言之:

对于边界情况,当序列长度为 11(只有根)或为 00(空树)时,只有一种情况,即:

不同二叉搜索树的个数为 。我们将 构建不同左子树的数量表示为 , 从 构建不同右子树的数量表示为 ,注意到 和序列的内容无关,只和序列的长度有关。于是,。 因此,我们可以得到以下公式:

可得:

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 1; j <= i; j++){
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}
  1. 方法二:数学

事实上我们在方法一中推导出的 G(n)函数的值在数学上被称为卡塔兰数 。卡塔兰数更便于计算的定义如下:

class Solution {
    public int numTrees(int n) {
        // 提示:我们在这里需要用 long 类型防止计算过程中的溢出
        long dp = 1;
        for (int i = 0; i < n; ++i) {
            dp = dp * 2 * (2 * i + 1) / (i + 2);
        }
        return (int) dp;
    }
}

链接leetcode-cn.com/problems/un…



我的公众号:GitKid

暂时每日分享LeetCode,我在不断学习的过程中,公众号也在不断充实,欢迎大家扫码关注。

TODO:动态规划专题

微信公众号
微信公众号

本文使用 mdnice 排版