算法训练营打卡Day19

news2024/11/29 22:47:55

目录

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

2.二叉树中的插入操作

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

题目1、二叉搜索树的最近公共祖先

力扣题目链接(opens new window)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

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

示例 1:

  • 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
  • 输出: 6
  • 解释: 节点 2 和节点 8 的最近公共祖先是 6。

思路

我们从二叉搜索树的根节点开始遍历,通过判断传入的两个参数节点的值,与根节点的值的大小关系,然后判断从根节点的左子树还是右子树开始搜索,如果遍历到叶子结点时还没有搜索到参数节点的值,我们就可以返回NULL;我们可以使用递归的思路求解, 最终的返回值是对应的root。

代码实现

CPP

执行逻辑主要包括三种:p、q的值都比当前节点值大;p、q的值都比当前节点值小;p、q在当前节点的一左一右

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);
            if (left != NULL) {
                return left;
            }
        }

        if (cur->val < p->val && cur->val < q->val) {   // 右
            TreeNode* right = traversal(cur->right, p, q);
            if (right != NULL) {
                return right;
            }
        }
        return cur;
    }
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }
};

 Python

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if root.val > p.val and root.val > q.val:
            return self.lowestCommonAncestor(root.left, p, q)
        elif root.val < p.val and root.val < q.val:
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root

题目2、二叉树中的插入操作

力扣题目链接(opens new window)

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。

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

思路

对于二叉搜索树中的插入操作,我能不能把二叉搜索树先转换成有序数组,再把给定的整数插入转换后的有序数组,最后进行中序遍历来得到新的二叉搜索树?

理论上可行,但是显然不是最优解。 

  1. 时间复杂度

    • 转换BST为有序数组:这个操作需要中序遍历BST,时间复杂度为O(n),其中n是BST的节点数。
    • 在有序数组中插入元素:在有序数组中插入一个元素需要找到正确的位置,并可能移动其他元素,时间复杂度为O(n)。
    • 从有序数组重建BST:将有序数组转换为BST的过程(例如,使用递归的方式构建平衡BST)通常也有O(n)的时间复杂度。

    综合起来,这个方法的总时间复杂度为O(n) + O(n) + O(n) = O(3n),即O(n)。虽然从表面上看这个时间复杂度与直接在BST中插入的时间复杂度O(log n)(在平衡BST中)或O(n)(在最坏情况下,如退化为链表)相比没有显著区别,但实际操作中会有额外的空间开销和步骤,且可能破坏BST的平衡性。

 我们可以不考虑题目中提示所说的改变树的结构的插入方式,只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。

代码实现

CPP

假如遇到空节点,我们直接插入并返回该节点;假如当前节点的值大于要插入的值,我们在左子树进行插入操作,否则在右子树操作。我们可以使用递归的思路。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root == NULL) {
            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;
    }
};

python

class Solution:
    def insertIntoBST(self, root, val):
        if root is None:
            node = TreeNode(val)
            return node

        if root.val > val:
            root.left = self.insertIntoBST(root.left, val)
        if root.val < val:
            root.right = self.insertIntoBST(root.right, val)

        return root

题目3、删除二叉搜索树中的节点

力扣题目链接(opens new window)

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

首先找到需要删除的节点; 如果找到了,删除它。 说明: 要求算法时间复杂度为 $O(h)$,h 为树的高度。

示例:

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

思路

首先我们自然地回去想找到需要删除的节点,如果找到目标节点,我们就删除它。但是我们要考虑节点是叶子结点、只拥有左子树/右子树的节点、根节点的情况,听上去会比较复杂。

分类讨论:

  1. 第一种情况:没找到删除的节点,遍历到空节点直接返回了
  2. 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
  3. 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
  4. 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
  5. 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

代码实现

CPP

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;
    }
};

python

class Solution:
    def deleteNode(self, root, key):
        if root is None:  # 如果根节点为空,直接返回
            return root
        if root.val == key:  # 找到要删除的节点
            if root.right is None:  # 如果右子树为空,直接返回左子树作为新的根节点
                return root.left
            cur = root.right
            while cur.left:  # 找到右子树中的最左节点
                cur = cur.left
            root.val, cur.val = cur.val, root.val  # 将要删除的节点值与最左节点值交换
        root.left = self.deleteNode(root.left, key)  # 在左子树中递归删除目标节点
        root.right = self.deleteNode(root.right, key)  # 在右子树中递归删除目标节点
        return root

#如果二叉搜索树改成普通二叉树,思路和代码实现又会变得不同

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

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

相关文章

温度转换-C语言

1.问题&#xff1a; 输入一个华氏温度&#xff0c;要求输出摄氏温度。公式为 c5(F-32)/9&#xff0c;取位2小数。 2.解答&#xff1a; scanf("%lf",&f);或者scanf("%f",&f);如果你前面定义的f是用double类型的话&#xff0c;就应该用%lf格式&…

deploy thingsboard

ThingsBoard部署 平台&#xff1a;windows10&#xff0c;idea2022&#xff0c;postgres15 maven仓库 进入thingsboard源码下载目录: 主要执行以下两个命令&#xff1a; mvn编译&#xff1a; mvn clean install -Dmaven.test.skiptrue编译报错时&#xff1a; 清除java进程 t…

计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

UART通信—基于江科大源码基础进行的改进和解析

我就不讲理论了&#xff0c;CSDN上大佬属实多&#xff0c;我就只讲代码了&#xff0c;串口的基本理论&#xff0c;大家去看其他大佬写的吧 一、源文件的组成 1、包含的头文件 stm32f10x.h 是STM32F10x系列微控制器的标准外设库&#xff08;Standard Peripheral Library&…

C语言基础(7)之操作符(1)(详解)

目录 1. 各种操作符介绍 1.1 操作符汇总表 2. 移位操作符 2.1 移位操作符知识拓展 —— 原码、反码、补码 2.2 移位操作符讲解 2.2.1 右移操作符 ( >> ) 2.2.2 左移操作符 ( << ) 3. 位操作符 3.1 & (按位与) 3.2 | (按位或) 3.3 ^ (按位异或) 3.4…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习&#xff08;一&#xff09;&#xff1a;总体架构》提到&#xff0c;Transformer 模型的主要缺点是&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以&#xff0c;许多次二次时间架构&#xff08;指一个函数或算法的增长速度小于二次…

C++ 多态:重塑编程效率与灵活性

目录 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的两个例外&#xff1a; 1. 协变(基类与派生类虚函数返回值类型不同) 2. 析构函数的重写(基类与派生类析构函数的名字不同&#xff09; 析构函数要不要定义成虚函数&#xff1f;&…

绝对值得收藏!分享7款ai写作论文免费一键生成网站

在当前的学术研究和写作过程中&#xff0c;AI写作工具已经成为了许多研究者和学生的重要助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助生成高质量的论文内容。以下是七款免费的AI写作论文生成器&#xff0c;其中特别推荐千笔-AIPassPaper。 1.千笔-AIPassPaper 千…

信号处理: Block Pending Handler 与 SIGKILL/SIGSTOP 实验

1. 信号处理机制的 “三张表” kill -l &#xff1a;前 31 个信号为系统标准信号。 block pending handler 三张表保存在每个进程的进程控制块 —— pcb 中&#xff0c;它们分别对应了某一信号的阻塞状态、待处理状态以及处理方式。 block &#xff1a;通过 sigset_t 类型实现&…

YOLO11改进 | 检测头 | 融合渐进特征金字塔的检测头【AFPN3】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一个渐进特征金字塔网络&…

关于 S7 - 1200 通过存储卡进行程序更新

西门子S7-1200系列PLC可以通过存储卡进行程序的更新&#xff0c;固件版本的升级以及程序数据的存储多项功能。本例进行程序更新的操作。 存储卡的订货号以及存储容量 一&#xff1b;如何插入存储卡 在CPU断电下&#xff0c;将CPU上挡板向下掀开&#xff0c;可以看到右上角有一…

ai写作论文会被检测吗?分享市面上7款自动写论文网站

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI写作工具在学术界引起了广泛关注。然而&#xff0c;这些工具的使用也引发了关于学术诚信和检测机制的讨论。根据多所高校的声明&#xff0c;为了应对AI代写论文的现象&#xff0c;许多高校已经开始引入论文检测工具…

Python入门:深入了解__init__.py 文件(如何实现动态导入子模块)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 `__init__.py` 的作用示例:📝 如何编写 `__init__.py`1. 空的 `__init__.py`2. 导入子模块3. 初始化代码4. 动态导入子模块📝 编写 `__init__.py` 的技巧和注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在…

01:(寄存器开发)点亮一个LED灯

寄存器开发 1、单片机的简介1.1、什么是单片机1.2、F1系列内核和芯片的系统架构1.3、存储器映像1.4、什么是寄存器 2、寄存器开发模板工程3、使用寄存器点亮一个LED4、代码改进15、代码改进2 本教程使用的是STM32F103C8T6最小系统板&#xff0c;教程来源B站up“嵌入式那些事”。…

前缀和(6)_和可被k整除的子数组_蓝桥杯

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(6)_和可被k整除的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

kubeadm部署k8s

1.1 安装Docker [rootk8s-all ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo [rootk8s-all ~]# sed -i sdownload.docker.commirrors.huaweicloud.com/docker-ce /etc/yum.repos.d/docker-ce.repo [ro…

基于Keras的U-Net模型在图像分割与计数中的应用

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&a…

Yocto - 使用Yocto开发嵌入式Linux系统_07 构建使用的临时文件夹

Detailing the Temporary Build Directory 在本章中&#xff0c;我们将尝试了解映像生成后临时构建目录的内容&#xff0c;并了解 BitBake 如何在烘焙过程中使用它。此外&#xff0c;我们还将了解这些目录中的某些内容如何在出现问题时作为有价值的信息来源来帮助我们。 In thi…

前缀和——从LeetCode题海中总结常见套路

目录 前缀和定义 截断前缀和DP&#xff1a;LeetCode53.最大子序和 经典左右指针&#xff1a;LeetCode209.长度最小的子数组 暴力求解&#xff1a;超时 优雅的双指针写法一&#xff1a; 优雅的双指针写法二&#xff1a; LeetCode.1588.所有奇数长度子数组的和 手速题&am…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…