【月度刷题计划同款】从区间 DP 到卡特兰数

news2024/12/25 23:41:55

题目描述

这是 LeetCode 上的 「96. 不同的二叉搜索树」 ,难度为 「中等」

Tag : 「树」、「二叉搜索树」、「动态规划」、「区间 DP」、「数学」、「卡特兰数」

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

返回满足题意的二叉搜索树的种数。

示例 1: alt

输入:n = 3

输出:5

示例 2:

输入:n = 1

输出:1

提示:

区间 DP

沿用 95. 不同的二叉搜索树 II 的基本思路,只不过本题不是求具体方案,而是求个数。

除了能用 95. 不同的二叉搜索树 II 提到的「卡特兰数」直接求解 个节点的以外,本题还能通过常规「区间 DP」的方式进行求解。

求数量使用 DP,求所有具体方案使用爆搜,是极其常见的一题多问搭配。

「定义 为使用数值范围在 之间的节点,所能构建的 BST 个数」

不失一般性考虑 该如何求解,仍用 中的根节点 i 为何值,作为切入点进行思考。

根据「BST 定义」及「乘法原理」可知: 相关节点构成的 BST 子树只能在 i 的左边,而 相关节点构成的 BST 子树只能在 i 的右边。所有的左右子树相互独立,因此以 i 为根节点的 BST 数量为 ,而 i 共有 个取值( )。

即有:

不难发现,求解区间 BST 数量 依赖于比其小的区间 ,这引导我们使用「区间 DP」的方式进行递推。

不了解区间 DP 的同学,可看 前置 🧀

一些细节:由于我们 i 的取值可能会取到区间中的最值 lr,为了能够该情况下, 能够顺利参与转移,起始我们需要先对所有满足 初始化为 1

Java 代码:

class Solution {
    public int numTrees(int n) {
        int[][] f = new int[n + 10][n + 10];
        for (int i = 0; i <= n + 1; i++) {
            for (int j = 0; j <= n + 1; j++) {
                if (i >= j) f[i][j] = 1;
            }
        }
        for (int len = 2; len <= n; len++) {
            for (int l = 1; l + len - 1 <= n; l++) {
                int r = l + len - 1;
                for (int i = l; i <= r; i++) {
                    f[l][r] += f[l][i - 1] * f[i + 1][r];
                }
            }
        }
        return f[1][n];
    }
}

C++ 代码:

class Solution {
public:
    int numTrees(int n) {
        vector<vector<int>> f(n + 2vector<int>(n + 20));
        for (int i = 0; i <= n + 1; i++) {
            for (int j = 0; j <= n + 1; j++) {
                if (i >= j) f[i][j] = 1;
            }
        }
        for (int len = 2; len <= n; len++) {
            for (int l = 1; l + len - 1 <= n; l++) {
                int r = l + len - 1;
                for (int i = l; i <= r; i++) {
                    f[l][r] += f[l][i - 1] * f[i + 1][r];
                }
            }
        }
        return f[1][n];
    }
};

Python 代码:

class Solution(object):
    def numTrees(self, n):
        f = [[0] * (n + 2for _ in range(n + 2)]
        for i in range(n + 2):
            for j in range(n + 2):
                if i >= j:
                    f[i][j] = 1
        for length in range(2, n + 1):
            for l in range(1, n - length + 2):
                r = l + length - 1
                for i in range(l, r + 1):
                    f[l][r] += f[l][i - 1] * f[i + 1][r]
        return f[1][n]

TypeScript 代码:

function numTrees(n: number): number {
    const f = new Array(n + 2).fill(0).map(() => new Array(n + 2).fill(0));
    for (let i = 0; i <= n + 1; i++) {
        for (let j = 0; j <= n + 1; j++) {
            if (i >= j) f[i][j] = 1;
        }
    }
    for (let len = 2; len <= n; len++) {
        for (let l = 1; l + len - 1 <= n; l++) {
            const r = l + len - 1;
            for (let i = l; i <= r; i++) {
                f[l][r] += f[l][i - 1] * f[i + 1][r];
            }
        }
    }
    return f[1][n];
};
  • 时间复杂度:
  • 空间复杂度:

区间 DP(优化)

求解完使用 个连续数所能构成的 BST 个数后,再来思考一个问题:使用 个连续数,所能构成的 BST 个数又是多少。

答案是一样的。

「由 个连续数构成的 BST 个数仅与数值个数有关系,与数值大小本身并无关系」

由于可知,我们上述的「区间 DP」必然进行了大量重复计算,例如 同为大小为 的区间,却被计算了两次。

调整我们的状态定义:「定义 为考虑连续数个数为 时,所能构成的 BST 的个数」

不失一般性考虑 如何计算,仍用 中哪个数值作为根节点进行考虑。假设使用数值 作为根节点,则有 BST 可贡献到 ,而 共有 个取值( )。

即有:

同时有初始化 ,含义为没有任何连续数时,只有“空树”一种合法方案。

Java 代码:

class Solution {
    public int numTrees(int n) {
        int[] f = new int[n + 10];
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                f[i] += f[j - 1] * f[i - j];
            }
        }
        return f[n];
    }
}

C++ 代码:

class Solution {
public:
    int numTrees(int n) {
        vector<intf(n + 100);
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                f[i] += f[j - 1] * f[i - j];
            }
        }
        return f[n];
    }
};

Python 代码:

class Solution:
    def numTrees(self, n: int) -> int:
        f = [0] * (n + 10)
        f[0] = 1
        for i in range(1, n + 1):
            for j in range(1, i + 1):
                f[i] += f[j - 1] * f[i - j]
        return f[n]

TypeScript 代码:

function numTrees(n: number): number {
    const f = new Array(n + 10).fill(0);
    f[0] = 1;
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= i; j++) {
            f[i] += f[j - 1] * f[i - j];
        }
    }
    return f[n];
};
  • 时间复杂度:
  • 空间复杂度:

数学 - 卡特兰数

在「区间 DP(优化)」中的递推过程,正是“卡特兰数”的 递推过程。通过对常规「区间 DP」的优化,我们得证 95. 不同的二叉搜索树 II 中「给定 个节点所能构成的 BST 的个数为卡特兰数」这一结论。

对于精确求卡特兰数,存在时间复杂度为 的通项公式做法,公式为

Java 代码:

class Solution {
    public int numTrees(int n) {
        if (n <= 1return 1;
        long ans = 1;
        for (int i = 0; i < n; i++) ans = ans * (4 * i + 2) / (i + 2);
        return (int)ans;
    }
}

C++ 代码:

class Solution {
public:
    int numTrees(int n) {
        if (n <= 1return 1;
        long long ans = 1;
        for (int i = 0; i < n; i++) ans = ans * (4 * i + 2) / (i + 2);
        return (int)ans;
    }
};

Python 代码:

class Solution:
    def numTrees(self, n: int) -> int:
        if n <= 1:
            return 1
        ans = 1
        for i in range(n):
            ans = ans * (4 * i + 2) // (i + 2)
        return ans

TypeScript 代码:

function numTrees(n: number): number {
    if (n <= 1return 1;
    let ans = 1;
    for (let i = 0; i < n; i++) ans = ans * (4 * i + 2) / (i + 2);
    return ans;
};
  • 时间复杂度:
  • 空间复杂度:

最后

这是我们「刷穿 LeetCode」系列文章的第 No.96 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows11系统下配置JAVA环境变量

一、环境变量的配置 1、右键开始菜单按钮&#xff0c;点击【系统➡高级系统设置】 2、在弹出的系统属性界面点击环境变量 3、在弹出的“环境变量”框&#xff0c;中选择下方的系统变量&#xff0c;点击新建 4、在弹出的“新建系统变量”框中&#xff0c;输入变量名和变量值&am…

vue中v-model应用于表单元素

v-model应用于表单元素 常见的表单元素都可以用v-model绑定关联→快速获取或设置 表单元素的值它会根据控件类型自动选取正确的方法来更新元素 常见的表单元素&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&…

RHCSA-VM-Linux安装虚拟机后的基础命令

1.代码命令 1.查看本机IP地址&#xff1a; ip addr 或者 ip a [foxbogon ~]$ ip addre [foxbogon ~]$ ip a 1&#xff1a;<Loopback,U,LOWER-UP> 为环回2网卡 2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP>为虚拟机自身网卡 2.测试网络联通性&#xff1a; [f…

032:vue中三元运算, style、class、type、 event等多种场景示例

第032个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

包装类和认识泛型

目录 一、包装类 1.1 基本数据类型和对应的包装类 1.2 装箱和拆箱 1.3 自动装箱和自动拆箱 二、泛型概念及引出 2.1 泛型概念 2.2 泛型引出 三、泛型类的使用 四、裸类型&#xff08;了解&#xff09; 五、泛型如何编译 5.1 擦除机制 5.2 为什么不能实例化泛型类型的数组 …

windows拷贝文件到WSL最快速的方法

windows拷贝文件到WSL最快速的方法 在WSL用 sudo ls /mnt/* 列出系统所有的挂载盘&#xff0c;可以看到windows系统的所有盘都列出来了 找到你需要拷贝的文件位置&#xff0c;然后使用 cp命令就可以了。例如我要拷贝h盘下面的gitlab备份文件去wsl&#xff0c;用下面的命令即可…

C语言经典100(49)--#if #ifdef和#ifndef的综合应用

目录 #if #endif #ifdef #ifnde 代码 运行结果 #if 通过下面这段小代码来分析一下&#xff1a; #define DEBUG 1 //首先定义一个宏 #if DEBUG printf("Value of i: %d\n", i); printf("Value of j: %d\n", j); #endif 在预处理过程中&a…

Spring(五):Spring Boot 的配置和日志

Spring Boot中的配置文件起到了非常重要的作用。在Spring Boot中&#xff0c;可以通过配置文件来配置应用程序的一系列属性&#xff0c;包括数据库连接、服务器端口号、日志级别等等。通过配置文件&#xff0c;可以方便地对应用程序进行配置管理和集中管理。另外&#xff0c;Sp…

文心一言 VS 讯飞星火 VS chatgpt (91)-- 算法导论8.3 4题

四、用go语言&#xff0c;说明如何在 O(n) 时间内&#xff0c;对 0 到 n 3 − 1 n^3-1 n3−1 区间内的n个整数进行排序。 文心一言&#xff1a; 在 O(n) 时间内对 0 到 n^3-1 区间内的 n 个整数进行排序&#xff0c;可以使用基数排序&#xff08;Radix Sort&#xff09;算法…

如何快速、高效、免费地将Excel文件转换为PDF格式?

如果你经常需要处理Excel文件&#xff0c;那么你一定遇到过这样的情况&#xff1a;你想把Excel文件分享给别人&#xff0c;但是对方没有安装Office软件&#xff0c;或者使用的版本和你不一致&#xff0c;导致打开时出现格式错乱、公式失效、图表显示不正常等问题。这时候&#…

USB Server助力苏美达,Ukey连接虚拟前置机

众所周知&#xff0c;银行的专用Ukey&#xff0c;和所有USB设备一样&#xff0c;有一个无法在虚拟机中被识别和调用的大问题。近日&#xff0c;苏美达集团想将银企直连的前置机程序迁移到虚拟机中时&#xff0c;就遇到了这个问题——大量Ukey因为无法在虚拟机中识别连接&#x…

C++——vector

作者&#xff1a;几冬雪来 时间&#xff1a;2023年9月12日 内容&#xff1a;C部分vector知识讲解 目录 前言&#xff1a; 1.vector&#xff1a; 1.vector的本质&#xff1a; 2.vector书写&#xff1a; vector创建空间&#xff1a; vector与reserve和vector和resize&a…

当promise遇上generator该如何应对?记一次工作中遇到的问题

问题背景 我们项目中有个保存功能&#xff0c;但是这个保存是一个异步函数&#xff0c;内部很多逻辑&#xff0c;比如说校验表单数据&#xff0c;获取子组件数据&#xff0c;数据处理&#xff0c;数据提交给后端获取中间值&#xff0c;最后保存。说明一下&#xff0c;我们的项…

项目经理摆脱「计划无用」的秘诀!

项目经理面临的最大挑战是项目计划执行不到位&#xff0c;导致项目进度严重滞后。这种情况下&#xff0c;尽管他们手中握有项目计划&#xff0c;但实际上却形同虚设&#xff0c;几乎无法发挥应有的作用。 许多项目经理喜欢陈述一些既定的事实&#xff0c;强调一些难以克服的困…

虚幻动画系统概述

本文主要整理一下高层次的概述&#xff0c;方便后续查阅 1.动画流程 DCC产出动画文件 -> UE动画导入 -> 动画蓝图驱动&#xff08;类似unity的动画状态机&#xff09; ->动画后处理蓝图驱动&#xff08;例如修型骨&#xff0c;骨骼矫正等后期处理&#xff09; 2.动…

淘宝直通车质量分怎么提高?

1、直通车关键词新质量分是什么 &#xff08;1&#xff09;创意质量&#xff1a;就是获取流量的能力&#xff0c;和关键词在店铺中基本数据的展现 &#xff08;2&#xff09;买家体验&#xff1a;最终的成交能力&#xff0c;在有相关性的前提下&#xff0c;可以提升创意分和体…

【C++】C++多线程库的使用

C线程库的使用 一、线程库&#xff08;thread&#xff09;1、线程的id类2、线程对象的构造3、thread提供的其他成员函数4、this_thread命名空间5、线程函数的参数问题 二、互斥量库&#xff08;mutex&#xff09;1、mutex的种类2、lock_guard和unique_locklock_guardunique_loc…

marisa-trie——一个基于高效Trie树实现的快速高效字符串压缩存储、查询匹配工具实践

在前文中&#xff0c;讲到了因为实际项目的需要&#xff0c;调研了一下当前比较好用字符串查询匹配算法&#xff0c;感兴趣的话可以直接看下&#xff1a; 《pyahocorasick——基于AC自动机的python高效字符串匹配实践》 本文的主要目的同前文相同&#xff0c;这里主要是介绍一…

Linux 6.6 中的 SELinux 删除了 NSA 的引用

导读Security Enhanced Linux (SELinux) 二十年来一直是主线内核的一部分&#xff0c;它提供了一个实现访问控制安全策略的模块&#xff0c;现在广泛用于增强生产 Linux 服务器和其他系统的安全性。长期接触 Linux 的人可能不知道 SELinux 源自美国国家安全局 (NSA)。但是现在 …

centos 下 Makefile 独立模块编译ko

1、安装编译内核环境包 编译需要用到kernel 源码&#xff0c;centos 下需先安装 kernel-devel 包&#xff0c;要下与自己kernel 对应版本 yum install kernel-devel 2、首先从内核或自己写的模块&#xff0c;发到编译环境中 注&#xff1a;就像我自己拷贝一个 bcache 驱动的目…