【每日算法】

算法第15天| (二叉树part02)层序遍历、226.翻转二叉树(优先掌握递归)、101. 对称二叉树(优先掌握递归)

文章目录

  • 算法第15天| (二叉树part02)层序遍历、226.翻转二叉树(优先掌握递归)、101. 对称二叉树(优先掌握递归)
  • 一、层序遍历
  • 二、226. 翻转二叉树(优先掌握递归)
  • 三、101. 对称二叉树(优先掌握递归)


二叉树理论基础

一、层序遍历

代码随想录链接
二叉树中的层序遍历相当于图论中的广度优先搜索;
二叉树中的递归遍历相当于图论中的深度优先搜索。
二叉树本身只有父节点和子结点之间的连接,同一层之间的节点并无连接关系,也就没办法做到层序遍历,所以要借助一个队列,保存每一层中遍历过的元素。(图论中的广度优先搜索同样是依赖队列实现的,利用队列的先进先出)

每次弹出一个节点的时候,就把这个节点的左右孩子都加进去。这时候,队列里就会有上下两层的元素,就需要用size记录每一层有多少个元素,该层的元素是否遍历完了。当把上一层的元素弹出完,此时队列中还剩的元素个数就是本层的节点个数。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {// 首先定义一个队列, 用于存储将要访问的节点,以按层次顺序处理它们queue<TreeNode*> que;// 不能直接把root加到队列里,首先要保证root不为空(第一个元素也有可能为空,极端情况必须考虑)if (root != NULL) que.push(root);// 最终的结果用二维数组result保存// 每一个内层向量表示一层的节点值vector<vector<int>> result;// 遍历二叉树,终止条件是没有元素再添加到队列里while (!que.empty()) {// size用于记录当前层节点的个数,用于控制队列中弹出的节点数量// 第一层节点数量为1(只有一个根节点)int size = que.size();// 定义一个一维数组,把每一层的元素放进一维数组,最终的结果应该是二维数组,包含每一层(每一层的元素存在一个一维数组);最终的结果用二维数组result保存vector<int> vec;// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的//通过size控制本层的元素for (int i = 0; i < size; i++) {// 获取队列前端节点TreeNode* node = que.front();// 将节点弹出队列que.pop();// 将节点记录到一维数组里vec.push_back(node->val);// 将节点的左右孩子加入队列if (node->left) que.push(node->left);if (node->right) que.push(node->right);}result.push_back(vec);}return result;}
};

为什么前中后序遍历用栈,层序遍历用队列???
因为,前中后序遍历需要栈的前进后出,层序遍历要利用队列的先进先出。
1.前序、中序和后序遍历都是深度优先搜索(DFS)方法。DFS 的特点是尽可能深地探索节点的子树,直到到达叶节点为止。在这些遍历方法中,栈(Stack)是一种非常合适的数据结构,因为它具有后进先出(LIFO)的特点,使用栈可以帮助我们回溯和记录访问过的节点,适合用于回溯到上一个节点。前序遍历递归实现很简单,但用栈可以避免递归的函数调用开销。
2.层序遍历(广度优先搜索,BFS)的顺序是按层次逐层访问节点,即:访问当前层的所有节点,然后再访问下一层的节点。队列(Queue)是一种适合这种访问顺序的数据结构,因为它具有先进先出(FIFO)的特点,能够保证我们按层次顺序处理节点。

二、226. 翻转二叉树(优先掌握递归)

翻转二叉树
代码随想录链接

这道题目如果想清楚就是送分题。用前序和后序最直接,中序比较绕;非递归和层序遍历方法也可以。
采用先序遍历的思路:(中左右)

  1. 确定递归函数的返回值和参数:
    因为题目要求返回新的二叉树的根节点,所以函数的返回值类型就是节点的定义类型Tree Node*;定义函数invertTree,参数为传入的根节点。
  2. 确定终止条件:遇到空节点:if (root==Null) return root;(如果根节点原本就为空,直接return)
  3. 处理逻辑:交换此节点 root的左右孩子;(root在这里不是指的根节点,而是遍历的每一个节点)
    swap(root->left,root->right);
    前序递归代码:(后序只需要把swap放在左右后面)
class Solution {
public:TreeNode* invertTree(TreeNode* root) {if (root == NULL) return root;swap(root->left, root->right);  // 中invertTree(root->left);         // 左invertTree(root->right);        // 右//  swap(root->left, root->right);  // 中 (后序)return root;}
};

后序递归代码:

class Solution {
public:TreeNode* invertTree(TreeNode* root) {if (root == NULL) return root;invertTree(root->left);         // 左swap(root->left, root->right);  // 中invertTree(root->left);        // 右  (注意这里依然要写->left,因为先处理了左,翻转了左,原来的左子树变成了右子树,如果再处理右,则实际上是处理了两遍左子树,而没有处理右子树)return root;}
};

迭代法前序遍历:

class Solution {
public:TreeNode* invertTree(TreeNode* root) {if (root == NULL) return root;stack<TreeNode*> st;st.push(root);while(!st.empty()) {TreeNode* node = st.top();              // 中st.pop();swap(node->left, node->right);if(node->right) st.push(node->right);   // 右if(node->left) st.push(node->left);     // 左}return root;}
};

迭代法前中后序统一写法代码:

class Solution {
public:TreeNode* invertTree(TreeNode* root) {stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop();if (node->right) st.push(node->right);  // 右if (node->left) st.push(node->left);    // 左st.push(node);                          // 中st.push(NULL);} else {st.pop();node = st.top();st.pop();swap(node->left, node->right);          // 节点处理逻辑}}return root;}
};

层序遍历(广度优先):

class Solution {
public:TreeNode* invertTree(TreeNode* root) {queue<TreeNode*> que;if (root != NULL) que.push(root);while (!que.empty()) {int size = que.size();for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();swap(node->left, node->right); // 节点处理if (node->left) que.push(node->left);if (node->right) que.push(node->right);}}return root;}
};

三、101. 对称二叉树(优先掌握递归)

题目链接
代码随想录链接
二叉树类的题目,确定遍历顺序非常重要。
本题实际上是考察二叉树是否可以翻转的问题,考察的是同时处理两个二叉树的遍历过程,同时比较两个二叉树里边对应的节点的情况.
本题只能使用后序遍历;大体思路是,转换成判断该二叉树是否可以翻转的问题,如果左右子树翻转之后能够和原来相同,则该二叉树为对称二叉树。要先把左右子树都处理完了,都返回给根节点,根节点(中)才能直到左右子树是否可以翻转。
代码实现:定义一个函数,需要传入左子树和右子树,即把根节点的左子树的头节点和右子树的头节点传入进来,判断根节点的左子树和右子树是否是可以相互翻转的,如果可以的话,整个二叉树就是对称二叉树;
首先要想,什么情况下return true,什么情况下return false.
节点的左子树为空,右子树不为空;左子树不为空,右子树为空;左右子树均为空;左右子树均不为空且值不相等;左右子树均不为空且值相等,这时,需要继续向下遍历。

class Solution {
public:bool compare(TreeNode* left, TreeNode* right) {// 首先排除空节点的情况if (left == NULL && right != NULL) return false;else if (left != NULL && right == NULL) return false;else if (left == NULL && right == NULL) return true;// 排除了空节点,不必担心空指针异常了;再排除数值不相同的情况else if (left->val != right->val) return false;// 此时就是:左右节点都不为空,且数值相同的情况// 此时才做递归,做下一层的判断// 比较二叉树外侧的节点数值是否相等,也就是左节点的左孩子,右节点的右孩子bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右// 比较二叉树内侧的节点数值是否相等,也就是左节点的右孩子,右节点的左孩子bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左// 内外侧的节点数值均相等,才能说明下面的所有孩子是可以相互翻转的bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)(所以是后序遍历)return isSame;}bool isSymmetric(TreeNode* root) {if (root == NULL) return true;return compare(root->left, root->right);}
};

看不出来前中后序遍历的简洁代码:

class Solution {
public:bool compare(TreeNode* left, TreeNode* right) {if (left == NULL && right != NULL) return false;else if (left != NULL && right == NULL) return false;else if (left == NULL && right == NULL) return true;else if (left->val != right->val) return false;else return compare(left->left, right->right) && compare(left->right, right->left);}bool isSymmetric(TreeNode* root) {if (root == NULL) return true;return compare(root->left, root->right);}
};

使用队列
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:
在这里插入图片描述

class Solution {
public:bool isSymmetric(TreeNode* root) {if (root == NULL) return true;queue<TreeNode*> que;que.push(root->left);   // 将左子树头结点加入队列que.push(root->right);  // 将右子树头结点加入队列while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转TreeNode* leftNode = que.front(); que.pop();TreeNode* rightNode = que.front(); que.pop();if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的continue;}// 左右一个节点不为空,或者都不为空但数值不相同,返回falseif ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {return false;}que.push(leftNode->left);   // 加入左节点左孩子que.push(rightNode->right); // 加入右节点右孩子que.push(leftNode->right);  // 加入左节点右孩子que.push(rightNode->left);  // 加入右节点左孩子}return true;}
};

使用栈

细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。只要把队列原封不动的改成栈就可以了.

class Solution {
public:bool isSymmetric(TreeNode* root) {if (root == NULL) return true;stack<TreeNode*> st; // 这里改成了栈st.push(root->left);st.push(root->right);while (!st.empty()) {TreeNode* leftNode = st.top(); st.pop();TreeNode* rightNode = st.top(); st.pop();if (!leftNode && !rightNode) {continue;}if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {return false;}st.push(leftNode->left);st.push(rightNode->right);st.push(leftNode->right);st.push(rightNode->left);}return true;}
};

PS:真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。
在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。

参考链接:
作者:力扣官方题解
链接:
https://leetcode.cn/problems/binary-tree-level-order-traversal/solutions/241885/er-cha-shu-de-ceng-xu-bian-li-by-leetcode-solution/
https://leetcode.cn/problems/symmetric-tree/solutions/268109/dui-cheng-er-cha-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

相关文章

Elasticsearch index 设置 false,为什么还可以被检索到?

在 Elasticsearch 中&#xff0c;mapping 定义了索引中的字段类型及其处理方式。 近期有球友提问&#xff0c;为什么设置了 index: false 的字段仍能被检索。 本文将详细探讨这个问题&#xff0c;并引入列式存储的概念&#xff0c;帮助大家更好地理解 Elasticsearch 的存储和查…

基于STM32F030设计的多点温度采集系统(BC26+OneNet)

一、项目背景 随着物联网技术的迅猛发展&#xff0c;越来越多的智能设备应运而生&#xff0c;而温度采集系统是其中重要的一类。在现代工业和家庭生活中&#xff0c;温度对于生产、居住和储存等过程的控制有着非常重要的作用。因此&#xff0c;准确地采集环境温度数据并进行处…

HTML做成一个粒子漩涡特效页面

大家好&#xff0c;今天制作制作一个粒子漩涡特效的页面&#xff01; 先看具体效果&#xff1a; 要在一个单一的 index.html 页面中实现粒子漩涡特效&#xff0c;我们可以使用HTML、CSS和JavaScript&#xff08;不需要外部库&#xff09;。下面是一个简单的例子&#xff0c;展…

JWT 从入门到精通

什么是 JWT JSON Web Token&#xff08;JWT&#xff09;是目前最流行的跨域身份验证解决方案 JSON Web Token Introduction - jwt.ioLearn about JSON Web Tokens, what are they, how they work, when and why you should use them.https://jwt.io/introduction 一、常见会…

Git发布正式

一般我们开发都是在测试环境开发&#xff0c;开发完成后再发布到正式环境。 一.分支代码合并到主分支1.首先切换到自己的分支(比如分支叫&#xff1a;dev)git checkout dev2.把本地分支拉取下来git pull 或者 git pull origin dev3.切换到主分支mastergit checkout master4.更新…

【Vue】购物车案例-构建项目

脚手架新建项目 (注意&#xff1a;勾选vuex) 版本说明&#xff1a; vue2 vue-router3 vuex3 vue3 vue-router4 vuex4/pinia vue create vue-cart-demo需要勾选上vuex&#xff0c;由于这个项目只有一个页面&#xff0c;vuex可勾可不勾 将原本src内容清空&#xff0c;替换成教学…

【计算机网络基础】IP地址

文章目录 一、IP介绍IP地址和Mac地址IP地址分类 二、IPV4地址IPV4地址分类子网掩码进制转换方法8421法则转换法私网地址PNAT技术IP分配原则 三、IPv6地址IPV6组成IPV6分类IPV6特殊地址 四、VLSM可变长子网掩码划分子网VLSM优点 &#x1f308;你好呀&#xff01;我是 山顶风景独…

springboot+mqtt使用总结

1.软件的选型 1.1.使用免费版EMQX 1.1.1.下载 百度搜索的目前是会打开官网&#xff0c;这里提供下免费版的使用链接EMQX使用手册 文档很详细&#xff0c;这里不再记录了。 1.2.使用rabbitmq rabbitmq一般做消息队列用&#xff0c;作为mqtt用我没有找到详细资料&#xff0c…

2013年 阿拉斯加巴罗活动层厚度和土壤含水量

Pre-ABoVE: Active Layer Thickness and Soil Water Content, Barrow, Alaska, 2013 ABoVE前&#xff1a;阿拉斯加巴罗活动层厚度和土壤含水量&#xff0c;2013年 简介 文件修订日期&#xff1a;2018-01-10 数据集版本&#xff1a;1 摘要 该数据集提供了 2013 年 8 月在…

Java | Leetcode Java题解之第142题环形链表II

题目&#xff1a; 题解&#xff1a; public class Solution {public ListNode detectCycle(ListNode head) {if (head null) {return null;}ListNode slow head, fast head;while (fast ! null) {slow slow.next;if (fast.next ! null) {fast fast.next.next;} else {ret…

网络安全难学吗?2024该怎么系统学习网络安全?

学习网络安全需要循序渐进&#xff0c;由浅入深。很多人对网络安全进行了解以后&#xff0c;就打算开始学习网络安全&#xff0c;但是又不知道怎么去系统的学习。 网络安全本身的知识不难&#xff0c;但需要学习的内容有很多&#xff0c;其中包括Linux、数据库、渗透测试、等保…

linux-ubuntu20网卡驱动安装AX201

https://blog.csdn.net/vor234/article/details/131682778 联想拯救者Y7000P2023 Ubuntu20.04网卡驱动AX211安装 幻14 ubuntu20.04 AX210驱动安装 官网下载相应的驱动&#xff1a;https://www.intel.com/content/www/us/en/support/articles/000005511/wireless.html sudo a…

AI生成个性化壁纸

使用天工AI 将图片设置成桌面壁纸

vivado HW_ILA

HW_ILA 描述 集成逻辑分析器&#xff08;ILA&#xff09;调试核心允许您执行系统内监控 通过对内核上的调试探针&#xff0c;在实现的设计中对信号进行处理。您可以配置 ILA核心实时触发特定硬件事件&#xff0c;并在 以系统速度探测。 ILA调试核心可以通过从IP目录实例化ILA核…

【微信小程序】事件分类以及阻止事件冒泡

在微信小程序中&#xff0c;事件分为冒泡事件和非冒泡事件两大类&#xff0c;它们的区别在于事件是否能从原始触发组件开始&#xff0c;向父级组件传播&#xff08;即“冒泡”&#xff09;。 冒泡事件&#xff1a;当一个组件上的事件被触发后&#xff0c;不仅当前组件会接收到这…

stm32编写Modbus步骤

1. modbus协议简介&#xff1a; modbus协议基于rs485总线&#xff0c;采取一主多从的形式&#xff0c;主设备轮询各从设备信息&#xff0c;从设备不主动上报。 日常使用都是RTU模式&#xff0c;协议帧格式如下所示&#xff1a; 地址 功能码 寄存器地址 读取寄存器…

【数据结构(邓俊辉)学习笔记】图05——优先级搜索

文章目录 0. 概述1. BAG2. ADT 0. 概述 学习下优先级搜索 1. BAG 优先级搜索是非常广义的&#xff0c;概况来说&#xff0c;无论DFS 还是BFS从逻辑上来都属于这种搜索。 回忆下什么叫搜索或者遍历&#xff0c;对于像图这种数据结构里的元素逐一的没有重复的也没有遗漏的对它们…

360数字安全:2024年4月勒索软件流行态势分析报告

勒索软件传播至今&#xff0c;360 反勒索服务已累计接收到数万勒索软件感染求助。随着新型勒索软件的快速蔓延&#xff0c;企业数据泄露风险不断上升&#xff0c;勒索金额在数百万到近亿美元的勒索案件不断出现。勒索软件给企业和个人带来的影响范围越来越广&#xff0c;危害性…

第一百零二节 Java面向对象设计 - Java静态内部类

Java面向对象设计 - Java静态内部类 静态成员类不是内部类 在另一个类的主体中定义的成员类可以声明为静态。 例子 以下代码声明了顶级类A和静态成员类B&#xff1a; class A {// Static member classpublic static class B {// Body for class B goes here} }注意 静态成…