Studying-代码随想录训练营day20| 235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

第二十天,二叉树part07,二叉树搜索树加油加油💪

目录

235.二叉搜索树的最近公共祖先

701.二叉搜索树中的插入操作

450.删除二叉搜索树中的节点

拓展:普通二叉树的删除方式 

总结


235.二叉搜索树的最近公共祖先

文档讲解:代码随想录二叉搜索树的最近公共祖先

视频讲解:手撕二叉搜索树的最近公共祖先

题目:

学习:

昨天我们是在普通二叉树内找到公共祖先,采取的是后序遍历的方式,遍历所有的节点,直到找到目标值为止进行返回。

但本题是二叉搜索树,我们可以利用二叉搜索树的特点来进行公共祖先的查找。我们知道二叉搜索树中每个节点的左子树中的所有值一定小于该节点,右子树中的所有值一定大于该节点。依据此特点我们可以把遍历过程分为三种情况。

  1. p节点的值和q节点的值都小于当前遍历节点的值,则在当前节点的左子树进行寻找。
  2. p节点的值和q节点的值都大于当前遍历节点的值,则在当前节点的右子树进行寻找。
  3. p节点的值和q节点的值其中一个等于当前遍历节点的值,或者分别大于,小于当前遍历节点的值,则立马返回当前遍历的节点,该节点就是最小公共祖先。原因:①如果当前节点是p节点或者q节点的其中一个,由于我们是从上至下遍历的,因此剩下一个节点肯定在该节点的子树中,因此当前节点就是最小公共祖先。②当前节点不是p节点或者q节点,此时由于p节点和q节点分别位于当前节点的左右子树之中,因此当前节点肯定是最小公共祖先,否则往左遍历将错过右子树中的节点,往右遍历将错过左子树中的节点。

代码:递归法

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
private:
    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {
        if (cur == NULL) return cur;
                                                        // 中
        if (cur->val > p->val && cur->val > q->val) {   // 左
            TreeNode* left = traversal(cur->left, p, q);
            return left;

        }
        else if (cur->val < p->val && cur->val < q->val) {   // 右
            TreeNode* right = traversal(cur->right, p, q);
            return right;
        }
        else { //第三种情况
            return cur;
        }
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }
};

代码:迭代法

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL) return NULL;
        
        TreeNode* answer = root; //返回数组
        while(answer) {
            //依据二叉搜索树的特点,分为三种情况
            if(p->val > answer->val && q->val > answer->val) {
                answer = answer->right;
            }
            else if(p->val < answer->val && q->val < answer->val) {
                answer = answer->left;
            }
            else { //第三种情况
                break;
            }
        }
        return answer;
    }
};

701.二叉搜索树中的插入操作

文档讲解:代码随想录二叉搜索树中的插入操作

视频讲解:手撕二叉搜索树中的插入操作

题目:

学习:对于二叉搜索树来说只要插入的数据和原始二叉树中的节点不同,则肯定能插入树中并成为一个叶子节点。本题其实不用考虑题目中的改变树的结构的插入方式。

代码:递归法(本题需要插入节点并将更改后的树层层返回)

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        //确定终止条件
        if (root == nullptr) {
            TreeNode* node = new TreeNode(val);
            return node; //把新加入的节点进行返回
        }

        //确定单层递归逻辑
        if(root->val > val) {
            root->left = insertIntoBST(root->left, val);  //把更改后的树一层层往上返回
        }
        if(root->val < val) {
            root->right = insertIntoBST(root->right, val); //把更改后的树一层层往上返回
        }
        return root;
    }
};

450.删除二叉搜索树中的节点

文档讲解:代码随想录删除二叉搜索树中的节点

视频讲解:手撕删除二叉搜索树中的节点

题目: 

学习:

本题需要删除二叉搜索树的节点,我们首先要分析的是,删除的情况有哪些:

  1. 要删除的节点在二叉树中不存在,二叉树不需要修改。
  2. 要删除的是叶子节点,那直接删除叶子节点,返回nullptr即可。
  3. 要删除的是中间节点,但中间节点左孩子为空,右孩子不为空,让右孩子替代删除节点。
  4. 要删除的是中间节点,但中间节点右孩子为空,左孩子不为空,让左孩子替代删除节点。
  5. 要删除的节点中左右孩子都不为空,则可以将删除节点的左子树的头节点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。(补充一种我采取的方式,使用删除节点的后继或者前序来替代删除节点,但处理会复杂一些)

第5中情况动画说明:

代码:递归法

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
        if (root->val == key) {
            // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
            if (root->left == nullptr && root->right == nullptr) {
                ///! 内存释放
                delete root;
                return nullptr;
            }
            // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
            else if (root->left == nullptr) {
                auto retNode = root->right;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
            else if (root->right == nullptr) {
                auto retNode = root->left;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
            // 并返回删除节点右孩子为新的根节点。
            else {
                TreeNode* cur = root->right; // 找右子树最左面的节点
                while(cur->left != nullptr) {
                    cur = cur->left;
                }
                cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
                TreeNode* tmp = root;   // 把root节点保存一下,下面来删除
                root = root->right;     // 返回旧root的右孩子作为新root
                delete tmp;             // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
                return root;
            }
        }
        if (root->val > key) root->left = deleteNode(root->left, key);
        if (root->val < key) root->right = deleteNode(root->right, key);
        return root;
    }
};

代码:递归法(使用后继作为代替)

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        //确定终止条件
        //没有找到的话
        if(root == nullptr) return root;

        //找到了的话,分为4种情况
        if(root->val == key) {
            //第一种情况,要删除的是叶子节点,则直接返回空
            if(root->left == nullptr && root->right == nullptr) {
                //内存释放
                delete root;
                return nullptr;
            }
            //第二种情况,要删除的是中间节点,但是左子树为空,使用右子树节点替代
            else if(root->left == nullptr && root->right != nullptr) {
                TreeNode* tmp = root;
                root = root->right;
                delete tmp;
                return root;
            }
            //第三种情况,要删除的是中间节点,但是右子树为空,使用左子树节点替代
            else if(root->left != nullptr && root->right == nullptr) {
                TreeNode* tmp = root;
                root = root->left;
                delete tmp;
                return root;
            }
            //第四种情况,要删除的是中间节点,且左右子树都不为空
            //使用前驱或者后继节点替代
            else {
                //使用后继节点替代,即右子树的最左边的节点。
                TreeNode* cur = root->right; 
                TreeNode* pre = nullptr; //指向cur的前一个节点
                while(cur->left != nullptr ) {
                    pre = cur;
                    cur = cur->left;
                }
                if(pre == nullptr) { //说明一次循环没有进行,删除节点的右子树的根节点没有左边部分,则其自身就是删除节点的后继
                    cur->left = root->left; //把删除节点的左子树保留
                    delete root;
                    return cur;
                }
                else { //说明至少进入了循环一次
                    pre->left = cur->right; //先把cur分理出,并保存cur的右子树(可能为空)
                    cur->left = root->left; //将删除节点的左右子树释放
                    cur->right = root->right;
                    delete root;
                    return cur;
                }
            }
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }
};

拓展:普通二叉树的删除方式 

学习:对于普通二叉树的节点删除来说,可以通过和叶子节点交换值得方式,然后再进行删除。

代码中目标节点(要删除的节点)被操作了两次:第一次是和目标节点的右子树最左面节点交换。第二次直接被NULL覆盖了。

代码:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        if (root->val == key) {
            if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用
                return root->left;
            }
            TreeNode *cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }
};

总结

二叉搜索树的特点要牢记,利用好二叉树的特点能够更有效的进行算法设计。

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

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

相关文章

中国4个民族群体的全基因组DNA甲基化变异图谱首次发布

2023年4月&#xff0c;由西北工业大学联合复旦大学等院校在Science China Life Sciences上发表题为“Genome-wide DNA methylation landscape of four Chinese populations and epigenetic variation linked to Tibetan high altitude adaptation”的文章&#xff0c;该研究通过…

supOS数据采集及接入-IoT网关接入操作

IOT网关接入流程 一、准备阶段 蓝卓云账号注册 Note:如果已经有蓝卓云账号&#xff0c;请跳过注册步骤&#xff0c;直接看沙箱使用手册。 注册账号 沙箱申请 Note: 如果已经有沙箱环境&#xff0c;请跳过注册步骤&#xff0c;直接看supOS 采集器操作说明。申请沙箱时候&#…

主流中间件--Redis

NOSQL 什么是NOSQL NoSQL(NoSQL Not Only SQL )&#xff0c;意即“不仅仅是SQL”&#xff0c;它泛指非关系型的数据库。 关系型数据库&#xff1a;以关系(由行和列组成的二维表)模型建模的数据库。简单理解&#xff1a;有表的就是关系型数据库。 NOSQL分类 Redis 什么是Redi…

07 - matlab m_map地学绘图工具基础函数 - 绘制等高线

07 - matlab m_map地学绘图工具基础函数 - 绘制等高线 0. 引言1. 关于绘制m_contour2. 关于绘制m_contourf3. 关于绘制m_elev4. 结语 0. 引言 本篇介绍下m_map中添加绘制等高线的一系列函数及其用法&#xff0c;主要函数包括m_elev、m_contour、m_contourf还有一些函数也和绘制…

动物常见图像的图像分类数据集

常见动物图像分类数据集 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1zZnCUZuNlX6MjuZImlDjTw?pwd03b9 提取码&#xff1a;03b9 数据集信息介绍&#xff1a; 文件夹 大象 中的图片数量: 1446 文件夹 松鼠 中的图片数量: 1862 文件夹 河马 中的图片数量:…

web自动化(一)selenium安装环境搭建、DrissionPage安装

selenium 简介 selenium是企业广泛应用的web自动化框架 selenium 三大组件 selenium IDE 浏览器插件 实现脚本录制 webDriver 实现对浏览器进行各种操作 Grid 分布式执行 用例同时在多个浏览器执行&#xff0c;提高测试效率 问题&#xff1a;环境搭建复杂&#xff0c;浏览器版…

天才程序员周弈帆 | Stable Diffusion 解读(四):Diffusers实现源码解读

本文来源公众号“天才程序员周弈帆”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Stable Diffusion 解读&#xff08;四&#xff09;&#xff1a;Diffusers实现源码解读 接上一篇文章[天才程序员周弈帆 | Stable Diffusion 解读…

【python013】pyinstaller打包PDF提取脚本为exe工具

1.在日常工作和学习中&#xff0c;遇到类似问题处理场景&#xff0c;如pdf文件核心内容截取&#xff0c;这里将文件打包成exe可执行文件&#xff0c;实现功能简便使用。 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&#xff01; 3.欢迎点赞、关…

19.异常处理

学习知识&#xff1a;方法中&#xff0c;异常的抛出和捕获 Main.java&#xff1a; public class Main {public static void main(String[] args) {errtest errtest new errtest();try{errtest.testerr();} catch (ArithmeticException e) {System.out.println("这个方法…

计算机基础知识——面向对象:封装+继承+多态整理

面向对象三大特性&#xff1a;封装、继承、多态。 1.封装 将一系列相关事物的共同的属性和行为提取出来&#xff0c;放到一个类中&#xff0c;同时隐藏对象的属性和实现细节&#xff0c;仅对外提供公共的访问方式。 【JavaBean类就可以看作是封装的完美案例。】 setter和get…

【Docker】rancher 管理平台搭建

目录 1. 所有节点安装docker 2. 所有节点配置/etc/sysconfig/docker 文件修改如下配置 3. 配置证书 4. 镜像仓库导入镜像 5. 创建镜像仓库 5.1 查询上传的 image id 5.2 镜像打标签 5.3 镜像上推 6. server 节点 7. client 节点 8. 在 server 节点启动 9. 查看运行…

聚酰胺-酰亚胺(PAI)应用前景广阔 酰氯法和异氰酸酯法为其主流制备方法

聚酰胺-酰亚胺&#xff08;PAI&#xff09;应用前景广阔 酰氯法和异氰酸酯法为其主流制备方法 聚酰胺-酰亚胺又称PAI&#xff0c;是一种分子链呈酰亚胺环和酰胺键有规则交替排列的高性能热塑性树脂。PAI具有耐磨耗性能好、摩擦系数低、尺寸稳定性好、耐高温、耐辐射、化学稳定性…

AI 编程还有前景嘛?

自从各个大厂相继出品 AI 编程助手之后&#xff0c;AI 在编程领域的发展&#xff0c;可谓是几无寸进。 相比于 AI 在多模态领域火热&#xff0c;AI 在编程领域的热度已经完全下来了。 阿七在公众号搜索了关键词「AI编程」&#xff0c;发现搜索出来的公众号寥寥无几&#xff0…

Python读取中文路径,出现乱码问题解决方案

Python读取中文路径&#xff0c;出现乱码问题解决方案 一、问题描述二、问题解决 欢迎学习交流&#xff01; 邮箱&#xff1a; z…1…6.com 网站&#xff1a; https://zephyrhours.github.io/ 一、问题描述 笔者在使用opencv读取带有中文路径的图片时&#xff0c;发现会出现乱…

ROS2中的CMakeLists(一)——基础知识

在使用ROS2框架开发机器人应用时&#xff0c;对各个功能包Cmakelist.txt文件的更改尤为重要。本系列旨在总头开始介绍Cmakelist.txt各条语句的意义和内涵。 Cmake已经是高度集成的构建工具&#xff0c;其作用是在不同开发环境下生成makefile文件&#xff0c;以此来执行make指令…

YOLOv8/v10项目使用教程

根据改好的YOLOv8.yaml改yolov10.yaml教程 打开ultralytics/cfg/models/v8路径&#xff0c;找到需要移植的yaml文件&#xff0c;从其中复制相关的模块。打开一个YOLOv10的yaml文件。 注释掉之前相应位置的模块&#xff0c;并粘贴上面复制的模块&#xff0c;完成。 其余使用步骤…

【Linux】使用ntp同步时间

ntp介绍 NTP&#xff08;Network Time Protocol&#xff0c;网络时间协议&#xff09;是一种用于同步计算机时间的协议&#xff0c;工作在UDP的123端口上。它是一种客户端-服务器协议&#xff0c;用于同步计算机的时钟。通过连接到网络上的时间服务器&#xff0c;计算机可以获…

企业变革的引擎:PDM实施的策略与实践

在当今快速发展的信息技术时代&#xff0c;产品数据管理PDM系统已成为企业提升效率和竞争力的重要工具。PDM不仅是一项技术&#xff0c;更是一种管理思想的应用&#xff0c;它涉及到企业组织、管理和产品开发过程的全面变革。本文将探讨PDM实施的四大关键技术&#xff0c;为企业…

【安卓13 源码】RescueParty救援机制

RescueParty机制正是在这个背景下诞生的&#xff0c;当它注意到系统或系统核心组件陷入循环崩溃状态时&#xff0c;就会根据崩溃的程度执行不同的救援行动&#xff0c;以期望让设备恢复到正常使用的状态。 开机后会自动重启&#xff0c;进入Recovery界面。经查找&#xff0c;是…

YOLOv8+SwanHub+作物检测:从可视化训练到Demo演示

1. 项目介绍 本项目旨在利用先进的YOLOv8深度学习模型对麦穗进行高效、准确的检测。我们采用了GlobalWheat数据集&#xff0c;该数据集包含丰富的麦穗图像&#xff0c;为模型的训练提供了有力的数据支持。通过该实验&#xff0c;实现高准确率的麦穗识别&#xff0c;为农业生产提…