力扣第95题 不同的二叉搜索树 II

ops/2024/12/12 12:36:26/

不同的二叉搜索树 II

  • 一级目录
    • 二级目录
      • 三级目录
  • 力扣第95题 - 不同的二叉搜索树 II
      • 题目描述
      • 思路分析
        • 1. 二叉搜索树的性质
        • 2. 递归构造树
        • 3. 动态规划优化(可选)
    • 递归详细
      • 递归函数定义
        • 参数含义
        • 返回值
      • 递归的逻辑
      • 递归过程的可视化
        • 第一次递归(范围 `[1, 3]`)
        • 第二次递归(范围 `[1, 1]` 和 `[3, 3]`)
        • 第三次递归组合(根节点 `2`)
        • 递归退出条件
      • 完整流程
      • 递归优势
      • 算法实现
        • C语言实现
      • 关键点解析
      • 复杂度分析
      • 输出示例
        • 输入:
        • 输出:

一级目录

二级目录

三级目录

力扣第95题 - 不同的二叉搜索树 II

题目描述

给你一个整数 n,要求生成所有由 1n 为节点所组成的 不同的二叉搜索树,并返回这些树的根节点。


思路分析

1. 二叉搜索树的性质
  • 左子树的所有节点值小于根节点值。
  • 右子树的所有节点值大于根节点值。
  • 1n 的每个数字作为根节点时,左右子树的节点范围可以分别确定。
2. 递归构造树
  • generateTrees(start, end) 表示生成从 startend 的所有 BST:
    • 如果 start > end,返回 [NULL](空树)。
    • 遍历 istartend,将 i 作为根节点:
      • 左子树:递归调用 generateTrees(start, i - 1)
      • 右子树:递归调用 generateTrees(i + 1, end)
    • 将所有可能的左子树和右子树组合,形成以 i 为根的所有树。
3. 动态规划优化(可选)
  • 利用备忘录保存 [start, end] 的计算结果,避免重复计算。

递归详细

递归函数定义

struct TreeNode** generateTreesHelper(int start, int end, int* returnSize)
参数含义
  1. start: 当前生成树的节点值范围的起始值。
  2. end: 当前生成树的节点值范围的结束值。
  3. returnSize: 用于返回生成的树的数量。
返回值

返回所有可能的 BST(以数组形式表示,数组中的每个元素是 BST 的根节点指针)。


递归的逻辑

  1. 递归结束条件(Base Case)
    if (start > end) {*returnSize = 1;struct TreeNode** result = (struct TreeNode**)malloc(sizeof(struct TreeNode*));result[0] = NULL; // 空树return result;
    }
    
    • start > end 时,不存在可用节点,这表示一种有效的情况:返回空树。
    • 这种情况允许更高层的递归在左右子树组合时将 NULL 作为子树。

  1. 枚举每个节点作为根节点
    for (int i = start; i <= end; i++) {
    
    • 遍历 [start, end] 范围内的每个数字,将其作为当前树的根节点。

  1. 递归生成左右子树
    int leftSize = 0;
    struct TreeNode** leftTrees = generateTreesHelper(start, i - 1, &leftSize);int rightSize = 0;
    struct TreeNode** rightTrees = generateTreesHelper(i + 1, end, &rightSize);
    
    • 左子树: 节点值范围为 [start, i-1]
    • 右子树: 节点值范围为 [i+1, end]
    • 对于每个可能的根节点 i,递归生成其所有可能的左子树和右子树。

  1. 组合左右子树
    for (int l = 0; l < leftSize; l++) {for (int r = 0; r < rightSize; r++) {struct TreeNode* root = createNode(i);root->left = leftTrees[l];root->right = rightTrees[r];result = (struct TreeNode**)realloc(result, sizeof(struct TreeNode*) * (totalSize + 1));result[totalSize++] = root;}
    }
    
    • 对于每个可能的左子树和右子树,组合成一棵以 i 为根的完整树:
      • leftTrees[l] 是某种可能的左子树。
      • rightTrees[r] 是某种可能的右子树。
    • 创建一个以 i 为值的新根节点 root
    • leftTrees[l] 连接到 root->left,将 rightTrees[r] 连接到 root->right
    • 将组合完成的树加入结果数组 result 中。

  1. 释放递归中动态分配的内存
    free(leftTrees);
    free(rightTrees);
    
    • 左右子树数组在当前递归结束后不再需要,及时释放内存,避免内存泄漏。

  1. 返回结果
    *returnSize = totalSize;
    return result;
    
    • 将生成的树的数量保存在 returnSize 中。
    • 返回所有可能的 BST。

递归过程的可视化

n = 3 为例,调用 generateTrees(3, &returnSize)

第一次递归(范围 [1, 3]
  • 枚举根节点:1, 2, 3
  • 例如,当根节点为 2
    • 左子树范围: [1, 1]
    • 右子树范围: [3, 3]

第二次递归(范围 [1, 1][3, 3]
  • 对于左子树 [1, 1]
    • 唯一根节点为 1,左右子树均为空。
  • 对于右子树 [3, 3]
    • 唯一根节点为 3,左右子树均为空。

第三次递归组合(根节点 2
  • 左子树为 [1]
  • 右子树为 [3]
  • 组合成以 2 为根的树:
        2/ \1   3
    

递归退出条件
  • 当范围无效(如 [4, 3]),返回空树 [NULL]

完整流程

递归通过分解问题,将大范围 [1, n] 的树拆分成多个小范围 [start, end] 的子树,最终组合成所有可能的二叉搜索树。


递归优势

  • 分治思想: 将问题拆解为左右子树独立生成的子问题,最后组合。
  • 灵活性: 能动态处理不同的 n,适用于范围大小不定的树生成问题。
  • 简洁性: 通过递归和迭代组合,代码结构清晰易读。

算法实现

C语言实现
#include <stdio.h>
#include <stdlib.h>// 定义树的结构
struct TreeNode {int val;struct TreeNode* left;struct TreeNode* right;
};// 创建一个新节点
struct TreeNode* createNode(int val) {struct TreeNode* node = (struct TreeNode*)malloc(sizeof(struct TreeNode));node->val = val;node->left = NULL;node->right = NULL;return node;
}// 生成从 start 到 end 的所有 BST
struct TreeNode** generateTreesHelper(int start, int end, int* returnSize) {if (start > end) {*returnSize = 1;struct TreeNode** result = (struct TreeNode**)malloc(sizeof(struct TreeNode*));result[0] = NULL;return result;}int totalSize = 0;struct TreeNode** result = NULL;for (int i = start; i <= end; i++) {// 左子树int leftSize = 0;struct TreeNode** leftTrees = generateTreesHelper(start, i - 1, &leftSize);// 右子树int rightSize = 0;struct TreeNode** rightTrees = generateTreesHelper(i + 1, end, &rightSize);// 组合左右子树和当前根节点for (int l = 0; l < leftSize; l++) {for (int r = 0; r < rightSize; r++) {struct TreeNode* root = createNode(i);root->left = leftTrees[l];root->right = rightTrees[r];result = (struct TreeNode**)realloc(result, sizeof(struct TreeNode*) * (totalSize + 1));result[totalSize++] = root;}}free(leftTrees);free(rightTrees);}*returnSize = totalSize;return result;
}// 主函数:生成从 1 到 n 的所有 BST
struct TreeNode** generateTrees(int n, int* returnSize) {if (n == 0) {*returnSize = 0;return NULL;}return generateTreesHelper(1, n, returnSize);
}// 测试用例打印树(先序遍历)
void printTree(struct TreeNode* root) {if (!root) {printf("null ");return;}printf("%d ", root->val);printTree(root->left);printTree(root->right);
}int main() {int returnSize;struct TreeNode** trees = generateTrees(3, &returnSize);printf("Generated %d unique BSTs:\n", returnSize);for (int i = 0; i < returnSize; i++) {printTree(trees[i]);printf("\n");}return 0;
}

关键点解析

  1. 递归终止条件:当 start > end 时返回空树 [NULL]
  2. 组合左右子树:
    • 遍历 startend,每个数都可能是根节点。
    • 对每个根节点,递归生成左右子树并组合。
  3. 内存管理:
    • 动态分配数组用于保存不同的树。
    • 组合树时需考虑 realloc 的正确使用。

复杂度分析

  1. 时间复杂度
    • 递归中,每次会分成多个子问题,时间复杂度近似为生成不同 BST 的卡特兰数 C n C_n Cn
      C n = 1 n + 1 ( 2 n n ) C_n = \frac{1}{n+1} \binom{2n}{n} Cn=n+11(n2n)
      因此时间复杂度为 O ( C n ) O(C_n) O(Cn)
  2. 空间复杂度
    • 递归栈的深度为 O ( n ) O(n) O(n)
    • 动态分配的内存和树的组合数量相关,为 O ( C n ) O(C_n) O(Cn)

输出示例

输入:
n = 3
输出:
Generated 5 unique BSTs:
1 null 2 null 3
1 null 3 2 null
2 1 null 3 null
3 1 null null 2
3 2 null null 1

这表示 5 种不同的二叉搜索树的先序遍历结果。


http://www.ppmy.cn/ops/141243.html

相关文章

SpringMvc完整知识点一

SpringMVC概述 定义 SpringMVC是一种基于Java实现MVC设计模型的轻量级Web框架 MVC设计模型&#xff1a;即将应用程序分为三个主要组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;。这种分离…

文字稿 | MatrixOne2.0.0:AI向量与高可用能力的重磅升级MatrixOne 2.0.0 新特性解读

MatrixOne 2.0.0 是一款 AI 驱动的云原生超融合数据库&#xff0c;采用了存算分离的架构&#xff0c;全面优化了云上资源利用效率。 MatrixOne兼容 MySQL 协议和语法&#xff0c;具备支持混合负载场景的能力&#xff0c;并结合向量数据类型、全文检索等特性&#xff0c;为生成式…

如何在Centos7中设置tomcat开机自启动

Tomcat已在centos中安装好&#xff0c;并且已配置好jdk的环境变量&#xff0c;但是Tomcat一直启动不起来。之前按照部署文档用的chkconfig进行Tomcat自启动配置&#xff0c;但是配置失败。现按照以下方法进行配置&#xff0c;配置成功。 1.配置tomcat8开机启动 在/usr/lib/sys…

(一)- DRM架构

一&#xff0c;DRM简介 linux内核中包含两类图形显示设备驱动框架&#xff1a; FB设备&#xff1a;Framebuffer图形显示框架; DRM&#xff1a;直接渲染管理器&#xff08;Direct Rendering Manager&#xff09;&#xff0c;是linux目前主流的图形显示框架&#xff1b; 1&am…

Hadoop生态圈框架部署(九-2)- Hive HA(高可用)部署

文章目录 前言一、Hive部署&#xff08;手动部署&#xff09;下载Hive1. 上传安装包2. 解压Hive安装包2.1 解压2.2 重命名2.3 解决冲突2.3.1 解决guava冲突2.3.2 解决SLF4J冲突 3. 配置Hive3.1 配置Hive环境变量3.2 修改 hive-site.xml 配置文件3.3 配置MySQL驱动包3.3.1 下在M…

自动化测试用例编写

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 自动化测试是使用专门的软件工具来验证软件解决方案&#xff0c;这通常涉及自动化功能作为测试过程的一部分。测试自动化最常见的对象是。 测试管理和缺陷管理单元…

一、使用 mdadm 工具在 Ubuntu 上创建 RAID 1(镜像)

在 Ubuntu 上创建 RAID 1&#xff08;镜像&#xff09;可以使用 mdadm 工具。以下是详细的步骤&#xff0c;包括安装必要的工具、创建 RAID 阵列、格式化并挂载 RAID 设备。 步骤一&#xff1a;安装 mdadm 首先确保你已经安装了 mdadm 包&#xff0c;这是管理软件 RAID 所需的…

C# 探险之旅:第四节 - 算术运算符

让我们继续你的C#探险之旅&#xff0c;这次聚焦于“算术运算符”。算术运算符在编程中用于执行基本的数学运算&#xff0c;如加法、减法、乘法、除法等。在C#中&#xff0c;这些运算符使用非常直观&#xff0c;并且支持多种数据类型。 1. 基本算术运算符 C# 提供了以下几种基…