LeetCode 题目 95:从递归到动态规划实现 不同的二叉搜索树 II

本文探讨了多种生成所有可能二叉搜索树的算法,包括递归分治法、动态规划、记忆化递归,详解每种方法的实现及优劣势。

题目描述

给定一个整数 n,生成所有由 1 到 n 为节点所组成的二叉搜索树 (BST)。

输入格式
  • n:表示生成树的节点值的上限。
输出格式
  • 返回所有存储结构为 TreeNode 的 BST 的列表。

示例

示例 1
输入: n = 3
输出: 
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]

解释:输出的数组表示 5 种不同结构的 BST 的根节点。


方法一:递归分治法

解题步骤
  1. 分治策略:对于每个数 i,使其为根节点,然后递归地生成所有可能的左子树和右子树。
  2. 递归构建:左子树由 [1, i-1] 生成,右子树由 [i+1, n] 生成。
完整的规范代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def generateTrees(n):
    """
    递归分治法生成所有可能的二叉搜索树
    :param n: int, 二叉搜索树中的节点数
    :return: List[TreeNode], 所有的二叉搜索树的列表
    """
    if n == 0:
        return []

    def build_trees(start, end):
        if start > end:
            return [None]  # 必须返回包含 None 的列表,以便在上一级递归中进行循环
        
        all_trees = []
        for i in range(start, end + 1):  # 枚举可行根节点
            # 获得所有可行的左子树集
            left_trees = build_trees(start, i - 1)
            # 获得所有可行的右子树集
            right_trees = build_trees(i + 1, end)
            
            # 从左子树集和右子树集中选出一棵左子树和一棵右子树,然后拼接到根节点
            for l in left_trees:
                for r in right_trees:
                    current_tree = TreeNode(i)
                    current_tree.left = l
                    current_tree.right = r
                    all_trees.append(current_tree)
        
        return all_trees

    return build_trees(1, n)

# 示例调用
result = generateTrees(3)
算法分析
  • 时间复杂度:(O(4^n / n^1.5)),基于卡特兰数的特性。
  • 空间复杂度:(O(4^n / n^1.5)),因为存储了所有可能的二叉搜索树。

方法二:动态规划

解题步骤
  1. 初始化:使用一个列表 dp,其中 dp[i] 存储所有包含 i 个节点的不同 BST。
  2. 动态规划填表:对于每个 i,通过拼接 dp[j-1](左子树)和 dp[i-j](右子树)的所有可能组合来构建。
完整的规范代码
def generateTrees(n):
    if n == 0:
        return []
    dp = [[] for _ in range(n + 1)]
    dp[0] = [None]
    
    for i in range(1, n + 1):
        for j in range(1, i + 1):
            for left in dp[j - 1]:
                for right in dp[i - j]:
                    root = TreeNode(j)
                    root.left = left
                    root.right = clone(right, j)  # 克隆右子树并偏移节点值
                    dp[i].append(root)
    return dp[n]

def clone(node, offset):
    if not node:
        return None
    root = TreeNode(node.val + offset)
    root.left = clone(node.left, offset)
    root.right = clone(node.right, offset)
    return root
算法分析
  • 时间复杂度:(O(4^n / n^1.5)),这与第一种方法的时间复杂度相同,因为基本操作和卡特兰数的特性类似。
  • 空间复杂度:(O(4^n / n^1.5)),存储所有可能的二叉搜索树。

方法三:记忆化递归

解题步骤
  1. 记忆化存储:使用字典或列表来缓存已计算的结果,避免重复计算相同的子问题。
  2. 递归构建:递归过程中检查是否已生成当前范围的二叉树,若已存在则直接返回。
完整的规范代码
def generateTrees(n):
    memo = {}

    def build_trees(start, end):
        if start > end:
            return [None]
        if (start, end) in memo:
            return memo[(start, end)]

        all_trees = []
        for i in range(start, end + 1):
            left_trees = build_trees(start, i - 1)
            right_trees = build_trees(i + 1, end)
            for l in left_trees:
                for r in right_trees:
                    root = TreeNode(i)
                    root.left = l
                    root.right = r
                    all_trees.append(root)
        
        memo[(start, end)] = all_trees
        return all_trees

    return build_trees(1, n) if n else []

# 示例调用
result = generateTrees(3)
算法分析
  • 时间复杂度:(O(4^n / n^1.5)),利用记忆化存储减少了重复计算。
  • 空间复杂度:(O(4^n / n^1.5)),用于存储所有可能的二叉搜索树以及记忆化的开销。

方法四:动态规划优化构建方式

解题步骤
  1. 动态规划构建:通过一个嵌套循环逐步构建所有可能的二叉树,同时使用动态规划表来记录每个节点数对应的所有可能树。
  2. 利用已有树进行构建:对于每个 i,利用之前构建的树通过复制并调整指针来创建新的树。
完整的规范代码
def generateTrees(n):
    if n == 0:
        return []
    dp = [[None] * (n + 1) for _ in range(n + 1)]
    for length in range(1, n + 1):
        for start in range(1, n - length + 2):
            end = start + length - 1
            dp[start][end] = []
            for root_val in range(start, end + 1):
                for left in dp[start][root_val - 1]:
                    for right in dp[root_val + 1][end]:
                        root = TreeNode(root_val)
                        root.left = left
                        root.right = right
                        dp[start][end].append(root)
    return dp[1][n]
算法分析
  • 时间复杂度:(O(4^n / n^1.5)),每个子问题都是独立计算。
  • 空间复杂度:(O(4^n / n^1.5)),动态规划表存储了所有中间结果。

不同算法的优劣势对比

特征方法一:递归分治法方法二:动态规划方法三:记忆化递归方法四:动态规划优化构建方式
时间复杂度(O(4^n / n^1.5))(O(4^n / n^1.5))(O(4^n / n^1.5))(O(4^n / n^1.5))
空间复杂度(O(4^n / n^1.5))(O(4^n / n^1.5))(O(4^n / n^1.5))(O(4^n / n^1.5))
优势直观、容易理解结构清晰、易于实现减少重复计算、提高效率利用已构建的树,提高构建效率
劣势计算重复,效率低空间占用大空间占用大,较复杂实现复杂,需要维护大量状态

应用示例

这些生成二叉搜索树的方法可以应用于算法设计与数据结构教学、计算机视觉中的决策树构建、以及任何需要生成多种状态树的应用中。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CNN实现fashion_mnist数据集分类(tensorflow)

1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、加载fashion_mnist数据与预处理 import numpy as np (train_images,train_labels),(test_images,test_labels) tf.keras.d…

[数据结构]————排序总结——插入排序(直接排序和希尔排序)—选择排序(选择排序和堆排序)-交换排序(冒泡排序和快速排序)—归并排序(归并排序)

文章涉及具体代码gitee: 登录 - Gitee.com 目录 1.插入排序 1.直接插入排序 总结 2.希尔排序 总结 2.选择排序 1.选择排序 ​编辑 总结 2.堆排序 总结 3.交换排序 1.冒泡排序 总结 2.快速排序 总结 4.归并排序 总结 5.总的分析总结 1.插入排…

抖音小风车一键跳转企业微信如何实现

我们在做抖音直播时,都喜欢挂上小风车去做转化,有的直播间小风车可以直接跳转到微信,这是怎么做到的呢?现在把这个经验给大家分享下: 首先我们需要先理解抖音直播间小风车是什么? 抖音小风车实际是一张直播…

c语言:打印任意行数的菱形

例如&#xff1a;以下图片形式 #include <stdio.h> int main() {int line 0;scanf_s("%d", &line);int i 0;//打印上半部分for (i 0; i < line; i){//打印空格数int j 0;for (j 0; j < line - 1 - i; j){printf(" ");}//打印*数量for…

内核中常用宏定义| container_of

文章目录 前言container_of函数介绍container_of函数实现container_of函数解析offsetof的使用container_of的使用结语 前言 前两篇我们写到内核中两种C语言高级语法__attribute__, __read_mostly。本篇写内核中另外一种常用宏定义之container_of container_of函数介绍 conta…

高级事件.

高级事件 1. 注册事件&#xff08;addEventListener)2.删除事件(removeEventListener&#xff09;3.DOM事件流4.事件对象及其方法&#xff08;当形参来看&#xff09;5.阻止默认事件/冒泡6.事件委托7.鼠标事件&#xff08;禁止右键/选中文字)8.鼠标事件对象8.常用键盘事件9.键盘…

【C++】模板初阶:泛型编程的起点

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

大模型下的Agent、AIGC的商业案例集合

算是一份摘录 1 AIGC 对影楼的影响 https://mp.weixin.qq.com/s/3j-6FAxZEEvXUZ1q6by2uw 2 出海Talkie &#xff1a;情感智能体 https://mp.weixin.qq.com/s/KHPmfuVvywxxcI2rqoOghA Talkie 为每条消息提供 3 个免费灵感&#xff0c;如果用户需要更多 AI 生成的灵感选项&…

Delta lake with Java--在spark集群上运行程序

昨天写了第一篇入门&#xff0c;今天看见有人收藏&#xff0c;继续努力学习下去。今天要实现的内容是如何将昨天的HelloDetlaLake 在spark集群上运行&#xff0c;。具体步骤如下 1、安装spark,我使用的是 spark-3.5.1-bin-hadoop3-scala2.13&#xff0c;去官网下载&#xff0c…

无穷级数错题本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <

2024五一赛数学建模A题B题C题完整思路+数据代码+参考论文

A题 钢板最优切割路径问题 &#xff08;完整资料在文末获取&#xff09; 1. 建立坐标系和表示方法&#xff1a; 在建模之前&#xff0c;我们需要将切割布局转换为数学表示。首先&#xff0c;我们可以将布局中的每个点表示为二维坐标系中的一个点。例如&#xff0c;B1可以表示…

Ubuntu服务器创建新用户及解决新用户登录Access denied问题

目录 Ubuntu服务器创建新用户及解决新用户登录Access denied问题创建账号步骤创建用户只创建用户添加用户到sudo组 允许账号远程连接重启ssh服务 删除账号要删除用户而不删除用户文件如果要删除并且删除用户的家目录和邮件 查询指令查看所有用户查询特定用户账户信息查看用户组…

【Java基础】Maven的生命周期(clean+site+default)

1. 前言 在 Maven 出现之前&#xff0c;项目构建的生命周期就已经存在&#xff0c;开发人员每天都在对项目进行清理&#xff0c;编译&#xff0c;测试及部署&#xff0c;但由于没有统一的规范&#xff0c;不同公司甚至不同项目之间的构建的方式都不尽相同。 Maven 从大量项目…

[C++基础学习-07]----C++结构体详解

前言 结构体&#xff08;Struct&#xff09;是C中一种用户定义的复合数据类型&#xff0c;用于存储不同类型的数据项。结构体可以包含不同类型的数据成员&#xff0c;这些数据成员可以是基本类型&#xff08;如int、float、char等&#xff09;&#xff0c;也可以是数组、指针、…

Linux编辑器——vim的基础使用

文章目录 1.vim的基本概念2.vim的基本操作3.vim命令模式命令集3.1移动光标3.2删除文字3.3复制3.4替换3.5撤销3.6更改3.7跳到指定的行 1.vim的基本概念 本文将介绍vim的三种模式&#xff0c;分别位&#xff1a;命令模式、插入模式、低行模式。他们的功能区分如下&#xff1a; 正…

2. 深度学习笔记--损失函数

在机器学习中&#xff0c;损失函数是代价函数的一部分&#xff0c;而代价函数则是目标函数的一种类型。 Loss function&#xff0c;即损失函数&#xff1a;用于定义单个训练样本与真实值之间的误差&#xff1b; Cost function&#xff0c;即代价函数&#xff1a;用于定义单个批…

学习和“劳动”相关的谚语,柯桥俄语培训

1. Бог труды́ лю́бит. 天道酬勤。 2. В ми́ре нет тру́дных дел, ну́жно лишь усе́рдие. 世上无难事,只怕有心人。 3. У́тро вечера мудренее. 一日之计在于晨。 4. Что посе́ешь,…

车载电子电器架构 —— 关于bus off汇总

车载电子电器架构 —— 关于bus off汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

[Java EE] 多线程(六):线程池与定时器

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (90平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

语义分割——铁路轨道数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 重…
最新文章