【数据结构】二叉树OJ练习

news2024/9/22 5:32:23

👑作者主页:@进击的安度因
🏠学习社区:进击的安度因(个人社区)
📖专栏链接:数据结构

文章目录

  • 一、二叉树的最小深度
  • 二、单值二叉树
  • 三、相同的树
  • 四、另一棵树的子树
  • 五、翻转二叉树
  • 六、对称二叉树
  • 七、二叉树的前序遍历
  • 八、平衡二叉树

今天为大家带来了 leetcode 中的八道二叉树OJ题,让我们一起刷题吧!

一、二叉树的最小深度

链接:111. 二叉树的最小深度

描述

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

示例1

img

输入:root = [3,9,20,null,null,15,7]
输出:2

示例2

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

提示:

  • 树中节点数的范围在 [0, 105]
  • -1000 <= Node.val <= 1000

思路

这里的二叉树有两种情况 同时具有左右子树只具有左子树或右子树

image-20221204195315785

对于 同时具有左右子树的情况 非常简单,可以借鉴上篇文章中我们求最大深度的接口的思路。

我们递归遍历到最后一层,返回较小的高度 + 1。每次遍历分别计算左右子树的高度,防止多次递归调用。

如果遇到 NULL 则返回0。

对于 只有单边子树 的情况就比较麻烦了,由于没有一边的子树,我们肯定是返回有子树的一边的高度。

但是如果套用 同时具有左右子树的情况 就不行了,到时候本来应该返回有子树的那一边,但是返回的总是1。

所以需要 两种情况分别处理

  • 首先求出左右子树高度和 左右子树中的最小高度。

  • 如果 当前树有左右子树 则返回最小高度 + 1;

  • 如果 当前树的左右子树为空 那么就返回 左边高度 + 右边高度 + 1,因为 有一边的高度必定为0,所以加上的高度并不影响正确结果。

image-20221204200848020

二、单值二叉树

链接:965. 单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

只有给定的树是单值二叉树时,才返回 true;否则返回 false

示例1

img
输入:[1,1,1,1,1,null,1]
输出:true

示例 2:

img
输入:[2,2,2,5,2]
输出:false

提示:

  1. 给定树的节点数范围是 [1, 100]
  2. 每个节点的值都是整数,范围为 [0, 99]

思路

单值树就是每个节点的值都相同

而一棵树是单值树,那么它的 根节点的值和左右子树的值一定相同,那么就可以拆分成子问题。

接下来判断各种情况:

  • 如果 节点为空 ,那么是单值二叉树,返回真;

  • 如果 左子树不为空且值 和 根节点值不相等 ,返回假;

  • 如果 右子树不为空且值 和 根节点值不相等, 返回假;

就这样分别递归左右子树,即可判断是否为单值二叉树。

image-20221204205443732

三、相同的树

链接:100. 相同的树

描述

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1

img

输入:p = [1,2,3], q = [1,2,3]
输出:true

示例2

img

输入:p = [1,2], q = [1,null,2]
输出:false

示例3

img

提示:

  • 两棵树上的节点数目都在范围 [0, 100]
  • -104 <= Node.val <= 104

思路

判断两棵树是否相等,我们先思考一下如何才算相等:

  • 对于 两棵空树 ,必定相等,返回真;

  • 如果 一棵树空,另一棵树不为空 ,那么不相等,返回假;

  • 如果 树中值相同但是结构不同 ,那么也不相等,返回假;

  • 如果 两棵树对应节点的值不相等,那么也不相等,返回假;

那么只要分别递归两棵的左右子树,如果一直递归到底都是真,且左右子树返回的结果都为真,那么这两棵树就相同。

image-20221204210510428

四、另一棵树的子树

链接:572. 另一棵树的子树

描述

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例1

img

输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true

示例2

输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false

提示:

  • root 树上的节点数量范围是 [1, 2000]
  • subRoot 树上的节点数量范围是 [1, 1000]
  • -10^4 <= root.val <= 10^4
  • -10^4 <= subRoot.val <= 10^4

思路

这道题算是 相同的树 的进阶版,如果没有我们上一题的铺垫,这题会写的很烦。

我们的主体思路是判断 二叉树的每一棵子树是否和 subRoot 相等

那么给出我们主要决策:

  • 由于 subRoot 一定不为空,所以一旦 root的子树为空,则返回假;
  • 如果 root 的子树 和 subRoot 相等,那么返回真;
  • 否则 递归左右子树,左右子树中任意一边找到了则 子树存在 。

而这里我们判断是否相等就可以直接复用 相同的树 了。

image-20221204222110569

完整代码:

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    if (p == NULL && q == NULL)
    {
        return true;
    }

    if (p == NULL || q == NULL)
    {
        return false;
    }

    if (p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
    // 子树不为空,如果树为空,则为假
    if (root == NULL)
    {
        return false;
    }

    // 如果找到了子树,则直接返回
    if (isSameTree(root, subRoot))
    {
        return true;
    }

    // 分别递归左右子树,子树只要找到了,则存在
    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

五、翻转二叉树

链接:226. 翻转二叉树

描述

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例1

img

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

示例2

img

输入:root = [2,1,3]
输出:[2,3,1]

示例3

输入:root = []
输出:[]

提示:

  • 树中节点数目范围在 [0, 100]
  • -100 <= Node.val <= 100

思路

对于翻转二叉树,就是把一棵树所有的左右子树对调,这里我们可以使用 后序遍历 的思想。

那么我们基本就可以写出我们的思路:

如果节点为空,那么无需翻转,返回 NULL

否则就先递归左子树,再递归右子树,到底后,开始交换左右子树,最后逐层返回。

image-20221204212744051

image-20221204213458246

六、对称二叉树

链接:101. 对称二叉树

描述

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例1

img

输入:root = [1,2,2,3,4,4,3]
输出:true

示例2

img

输入:root = [1,2,2,null,3,null,3]
输出:false

提示:

  • 树中节点数目在范围 [1, 1000]
  • -100 <= Node.val <= 100

思路1

一棵树是否轴对称,可以理解为 它的根节点的 某一边子树翻转 后是否和另一边为 相同的树

比如我们这边翻转左子树:

image-20221204214328801

我们上方 相同的树翻转二叉树 刚刚写过,那么写这种思路就很简单了。

这题我们也给出相应的决策:

  • 如果左右子树都为空,为根节点,那么返回真;
  • 如果左右子树一遍不为空,那么返回假;
  • 如果左右子树都不为空,那么给定 leftright 分别记录左右子树。翻转左边,记录右边;
  • 最后返回判断左右子树是否为相同的树。

接下来我们看看代码怎么写:

struct TreeNode* invertTree(struct TreeNode* root)
{
    if (root == NULL)
    {
        return NULL;
    }

    struct TreeNode* left = invertTree(root->left);
    struct TreeNode* right = invertTree(root->right);
    
    root->left = right;
    root->right = left;

    return root;
}

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
    if (p == NULL && q == NULL)
    {
        return true;
    }

    if (p == NULL || q == NULL)
    {
        return false;
    }
    
    if (p->val != q->val)
    {
        return false;
    }

    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
bool isSymmetric(struct TreeNode* root)
{
    // 左右子树都为空
    if (root->left == NULL && root->right == NULL)
    {
        return true;
    }

    // 左右子树一边不为空
    if (root->left == NULL || root->right == NULL)
    {
        return false;
    }

    // 左右子树两边都不为空
    struct TreeNode* left, *right;
    if (root->left && root->right)
    {
        // 翻转左边
        left = invertTree(root->left);
        right = root->right;
    }

    // 和右边子树比较,返回 bool 值
    return isSameTree(left, right);
}

image-20221204214856675

但是这种方法也是不太好的,因为破坏了原本二叉树的结构,那么还有更好的方法吗?看思路2

思路2

一棵树对称就是 它的左右子树呈镜像状态 。说白了就是节点左子树的值等于右子树的值,右子树的值等于左子树的值。

那么我们可以用一个 check 函数来递归检查,并将二叉树的根节点传两份过去:

  • 如果两棵树都为空,返回真;
  • 如果两棵树一棵为空,另一棵不为空,返回假;
  • 如果两棵树都不为空,但是值不相等,返回假;
  • 上面的走完都没有返回,那么就分别递归第一棵树的左子树和第二棵树的右子树;第一棵树的右子树和第二棵树的左子树。

最后返回 check 函数的真值,就能判断出结果。

image-20221204215725780

七、二叉树的前序遍历

链接:144. 二叉树的前序遍历

描述

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1

img

输入:root = [1,null,2,3]
输出:[1,2,3]

示例 2:

输入:root = []
输出:[]

示例3

输入:root = [1]
输出:[1]

示例 4:

img

输入:root = [1,2]
输出:[1,2]

示例 5

img

输入:root = [1,null,2]
输出:[1,2]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

思路

这里的前序遍历和之前的有所不同,题目要求我们将遍历结果存到数组中,将数组返回,且空指针不需要记录

那么我们可以计算出二叉树的大小,然后 动态开辟一个二叉树大小的数组

并使用一个下标来记录数组的元素个数,最后 前序遍历二叉树 ,将结果存入数组,返回数组。

image-20221204220730242

相关题目

  • 94. 二叉树的中序遍历
  • 145. 二叉树的后序遍历

相关题目和这道题思路类似,我就不带着大家看了,大家自己下去可以试试

八、平衡二叉树

链接:110. 平衡二叉树

描述

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例1

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例2

img

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

示例3

输入:root = []
输出:true

提示:

  • 树中的节点数在范围 [0, 5000]
  • -104 <= Node.val <= 104

思路

所谓 平衡二叉树就是任意节点的子树的高度差都小于等于1

基于这个理解,那么我们可以将它分成子问题:每个节点的子树的高度差小于等于1。

那么就可以给出思路:

  • 如果节点为空,则是完全二叉树;
  • 否则就求左右两边子树高度;
  • 再判断左右子树的 高度差的绝对值 是否 大于1 ,大于1则一定不是完全二叉树,返回假;
  • 最后分别递归左右子树,判断左右子树是否满足完全二叉树的条件。

求高度的就是我们上篇博客的 计算二叉树的高度 的接口。

image-20221204223134054

完整代码:

int TreeHeight(struct TreeNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    int leftHeight = TreeHeight(root->left);
    int rightHeight = TreeHeight(root->right);

    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

bool isBalanced(struct TreeNode* root)
{
    // 根节点为空,是完全二叉树
    if (root == NULL)
    {
        return true;
    }

    // 求左右两边高度
    int leftHeight = TreeHeight(root->left);
    int rightHeight = TreeHeight(root->right);

    // 绝对值大于1一定不是完全二叉树
    if (abs(leftHeight - rightHeight) > 1)
    {
        return false;
    }

    return isBalanced(root->left) && isBalanced(root->right);
}

到这里,本篇博客就到此结束了。二叉树的题目总体来说代码还是很少的,主要是递归的思路。大家可以下去多画画图!
如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!
我是anduin,一名C语言初学者,我们下期见!

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

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

相关文章

毫米波传感器原理介绍:测速_1相位

在前文中&#xff0c;我们分析了 IF信号的频率&#xff0c;并展示了该频率与物体到雷达的距离 成正比。在本文中&#xff0c;我们将探讨IF 信号的相位。如果我们希望了解 FMCW 雷达响应物体极小位移的能力&#xff0c;那么研究相位就非常重要。雷达正是凭此非常快速且准确地测量…

Java 基础之线程

线程是 cpu 可以调度的最小单元&#xff0c;多线程可以利用 cpu 轮询时间片的特点&#xff0c;在一个线程进入阻塞状态时&#xff0c;快速切换到其余线程执行其余操作&#xff0c;减少用户的等待响应时间。所以我们需要了解线程的基本概念&#xff0c;如何启动线程以及怎么去控…

最短路径(Dijkstra算法与Floyd算法)

一、Dijkstra算法 Dijkstra算法与之前学习过的Prim算法有些相似之处。我们直接通过一个例子来讲解 假设要求的是A->E之间的最短路径。首先我们来列出顶点A到其他各顶点的路径长度&#xff1a;A->D 2&#xff0c;A->B 6&#xff0c;A->C 1&#xff0c;A->E…

MySQL主从复制

MySQL主从复制 MySQL主从复制原理 主服务器在更新数据前&#xff0c;会写入硬盘&#xff0c;银盘在再将数据写入二进制日志 从服务器开启I/O线程&#xff0c;Master节点为每个I/O线程启动一个dump线程用于发送二进制事件到从服务器的中继日志中 从服务器的sql线程开启&…

springboot集成dubbo配置多注册中心

1 dubbo多注册中心 dubbo可以支持多注册中心&#xff0c;以及多协议, 本文示例dubbo同时注册到nacos和zookeeper注册中心&#xff1a; 在前文基础上&#xff0c;给provider consumer模块加上zookeeper依赖&#xff1a; <dependency><groupId>org.apache.dubbo<…

TypeScript26(TS进阶用法Record Readonly)

Readonly Readonly与我们上一章节学的Partial 很相似&#xff0c;只是把? 替换成了 Readonly // 源码 type Readonly<T> {readonly [P in keyof T]: T[P]; }; 疑问&#xff1a; keyof 是干什么的&#xff1f; in 是干什么的&#xff1f; Readonly 是将该属性变为…

【HBU】数据结构第一次月测题(线性结构)

数据结构第一次月测题 判断题&#xff1a; 1.在具有N个结点的单链表中&#xff0c;访问结点和增加结点的时间复杂度分别对应为O&#xff08;1&#xff09;和O&#xff08;N&#xff09; F 访问节点的时间复杂度为O(N) 2.对于顺序存储长度为N的线性表&#xff0c;…

DataBinding原理----双向绑定(4)

前面的几种文章分析了DataBinding单向数据绑定的原理&#xff0c;今天来看看双向数据绑定是怎么回事。 我们知道单向绑定是在数据发生变化的时候能够通知到UI&#xff0c;让数据的变化能够及时反应到UI上&#xff1b;而双向绑定则是不仅要让数据的变化能够反馈到UI上&#xff0…

web前端-javascript-立即执行函数(说明、例子)

立即执行函数 /* (function(){alert("我是一个匿名函数~~~"); })(); */(function (a, b) {console.log("a " a);console.log("b " b); })(123, 456);1. 说明 函数定义完&#xff0c;立即被调用&#xff0c;这种函数叫做立即执行函数立即执…

Twitter群推解锁流量大门的钥匙

Twitter作为全球最知名的社交媒体平台之一&#xff0c;对海外营销有着巨大的影响力&#xff0c;是外贸企业进行群推、群发、引流必不可少的平台。那么要想通过推特群推、推特群发打开流量的大门&#xff0c;这里有几点值得大家注意&#xff0c;帮助你更好的驾驭流量&#xff1a…

虚拟机安装zookeeper集群

一、准备 克隆原先的虚拟机;因为是从原先已有jdk和zk的linux虚拟机克隆过来的,所以克隆的虚拟机上是一样的! 三台虚拟机,我采用的是:zk的ip不一样,端口一样 修改每台虚拟机上环境变量,zk配置文件 修改zookeeper配置文件,采用默认端口,配置主从节点

Bootstrap主页面搭建(十四)

创建主页面&#xff1a;index.jsp&#xff1a; 引入bootstrap依赖&#xff1a; 首先写导航条&#xff0c;复制代码更改&#xff1a; <!--导航条--> <nav class"navbar navbar-inverse"><div class"container-fluid"><!-- Brand and…

Nginx配置实例-动静分离

1、什么是动静分离 Nginx动静分离简单来说就是把动态跟静态请求分开&#xff0c;不能理解成只是单纯的把动态页面和 静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开&#xff0c;可以理解成使用Nginx 处理静态页面&#xff0c;Tomcat处理动态页面。 动静分离从目…

Project joee 算法开发日志(一)

目录一. 下载并安装TensorRT1.1 下载安装TensorRT1.2 验证TensorRT安装是否成功二. 安装并测试Windows预测库2.1 安装cuda11.0_cudnn8.0_avx_mkl-trt7.2.1.6 预测库2.2 测试精度损失2.3 推理速度测试三. 总结开发机器配置&#xff1a;CPU: AMD5800 8core 16ThreadGPU: NVIDIA G…

微信支付回调,内网穿透详细过程

文章目录支付回调接口通过Ngrok进行内网穿透步骤1. 根据邮箱注册一个账号2. 获取隧道id3.下载Ngrok客户端4. 双击这个 Sunny-Ngrok启动工具.bat 文件5. 填写你的 隧道id 回车6.客户端启动成功7. 所以你的notify_url对应的value需要改为内网穿透的地址为8.支付成功之后微信平台会…

分面中添加直线

简介 这篇也是分享最近统计建模中所绘制的一副图形。总体而言和前面的几篇&#xff1a;xxx 类似。都是从“数据导入”到“基于分面的可视化”。但是本文的小技巧是&#xff0c;在不同的分面中添加直线。最后得到的图形如下&#xff1a; 注意&#xff1a;本文数据和代码在公众号…

交易所通用质押式回购

一、专业术语 逆回购&#xff1a;指资金融出方将资金融给资金融入方&#xff0c;收取有价证券作为质押&#xff0c;并在未来收回本息&#xff0c;并解除有价证券质押的交易行为。 债券通用质押式回购交易&#xff1a;&#xff08;简称“通用回购”&#xff09;是指资金融入方…

划分成绩ABCD

已知成绩等级划分为{“A”:[90~100],"B":[80~89],"c":[60~79],"D":[0~59]} 1、随机生成20个整数&#xff0c;范围0-100 2、按等级归类&#xff0c;输出成绩等级列表字典如下&#xff1a; {A: [96, 96, 97, 97, 100, 100], B: [86], C: [71, 7…

Python学习基础笔记二十二——生成器

一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值&#xff0c;但是yield又不同于return&#xff0c;return的执行意味着程序的结束&#xff0c;调用生成器函数不会得到返回的具体的值&#xff0c;而是得到一个可迭代的对象。每一次获取这个可迭代对…

微机原理与接口技术:数模转换和模数转换 详细笔记

文章目录1.数模转换1.1.数模转换原理1.1.1.权电阻D/A转换器1.1.2.R-2R T型电阻网络D/A转换器1.1.3.补充 D/A转换器的主要技术指标1.2.D/A转换芯片——DAC08321.2.1.引脚介绍1.2.2.工作方式直通输入方式单缓冲方式双缓冲方式2.模数转换2.1.信号变换中的采样、量化和编码2.1.1.采…