代码随想录算法训练营DAY22|C++二叉树Part.8|235.二叉搜索树的最近公共祖先、450.删除二叉搜索树中的结点

本文主要是介绍代码随想录算法训练营DAY22|C++二叉树Part.8|235.二叉搜索树的最近公共祖先、450.删除二叉搜索树中的结点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 235.二叉搜索树的最近公共祖先
    • 思路
    • 伪代码实现
    • CPP代码
    • 迭代法的CPP代码
  • 701.二叉搜索树中的插入操作
    • 思路
    • 伪代码
      • 递归函数有返回值
      • 递归函数不要返回值
      • 迭代方法
    • CPP代码
      • 递归有返回值
      • 递归无返回值
      • 迭代
  • 450.删除二叉搜索树中的结点
    • 思路(分析五种情况)
      • 没找到删除的点
      • 删的点是叶子结点
      • 要删的结点左为空,右为空
      • 要删的结点左不为空,右为空
      • 要删的结点左右都不为空
    • 伪代码实现
    • CPP总体代码
    • 二叉搜索树的迭代法删除结点
    • 普通二叉树的删除方式

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

力扣题目链接

文章讲解:235.二叉搜索树的最近公共祖先

视频讲解:二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先

状态:跟上一章思路一样文章链接。但是本题中我们不关心遍历顺序,因为BST的特性已经为我们确定好了二叉搜素树的特性。

思路

这个题目如何利用二叉搜索树的特性呢

当我们在遍历根结点的时候,

如果发现根结点比p和q的数值都大的话,说明p和q一定在我们根结点的左子树。所以这时就向左遍历。

如果发现根结点比q和p的数值都小的话,说明目标结点一定在我们根结点的右子树。所以这时就要想右遍历


如果我们的根结点已经到了p和q之间了呢

其实已经说明了该结点就是p和q的最小公共祖先了,因为我们无论再向哪边遍历,都会错过q或者q的。(这是本题中我认为最重要的逻辑)

伪代码实现

  • 递归函数的参数和返回值:

    • 返回值就是我们的公共祖先
    • 传参就是当前结点和p、q
    TreeNode* traversal (cur, p, q){
    }
    
  • 终止条件:其实都不需要这个终止条件,因为题中说了p和q为不同结点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况。

  if (cur == NULL) return NULL;
  • 单层递归条件:这里我们根本不关注遍历顺序和中结点的处理逻辑,因为我们的搜索树已经帮我们规定好了搜索路径。
//左
if (cur->val > p->val && cur->val > q->val){	//当前数值比p大,比q大left = traversal(cur->left, p, q);	//说明我们要向左去搜索if (left != NULL) return left; //这里说明我们已经找到公共祖先了
}
//右
if(cur->val < p->val && cur->val < q->val){right = traversal(cur->right, p, q);if (right != NULL) return right;//如果right不为空,说明我们在右子树找到了想要的结果
}
//当前数值在p和q之间
return cur;

CPP代码

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);}
};
//精简后的代码
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root->val > p->val && root->val > q->val) {return lowestCommonAncestor(root->left, p, q);} else if (root->val < p->val && root->val < q->val) {return lowestCommonAncestor(root->right, p, q);} else return root;}
};

迭代法的CPP代码

因为搜索的有序性,所以迭代法也很简单

while(cur){ //只要当前结点不为空,我们就一直搜索if (cur->val > p->val && cur->val > q->val)cur = cur->left;if (cur->val < p->val && cur->val < q->val)cur = cur->right;return cur;
}

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

力扣题目链接

文章讲解:701.二叉搜索树中的插入操作

视频讲解:原来这么简单? | LeetCode:701.二叉搜索树中的插入操作

状态:我们不管怎么样,直接插到叶子结点,让新插入到元素成为叶子结点即可。那么本题的两个难点显而易见:

  • 怎么找到待插元素应该去的位置
  • 怎么找到叶子结点呢?

思路

其实就是:无论我们插入什么样的结点,总可以在二叉搜索树的叶子结点找到它的位置

为什么我们不改变二叉树的结构,硬在里面插一个呢?那这就把这个题目做复杂了。我们题目并没有这样的要求

伪代码

递归函数有返回值

  • 确定递归函数参数和返回值

    • 参数–根结点和插入的数值
    • 返回值–插入新结点之后,我们这个新二叉树的根结点。
    TreeNode* insert(root, val){}
    
  • 确定终止条件:如果我们的root等于空,说明我们已经找到插入结点的位置了

    • 其中的return node是本段代码的精髓,一定要领会
if (root == NULL){TreeNode* node = new TreeNode(val);return node;//把新插入的结点向上一层返回,因为我们一层层向下递归到叶子结点了,返回给之前的叶子
}
  • 单层递归逻辑
if (val < root->val) root->left = insert(root->left, val); //还记得之前我们返回了新插入的结点吗,就是把他的位置返回给他了
if (val > root->val)root->right = insert(root->right, val);//至此我们就完成了我们的数值在叶子结点对应的位置
return root;

递归函数不要返回值

  • 确定递归函数参数和返回值,这里我要不要返回值,也就是说知道插入的结点位置,直接让其父结点指向插入结点,结束递归
TreeNode* parent; //记录遍历结点的父结点
void traversal(TreeNode* cur, int val)
  • 确定终止条件:既然没有返回值,我们就需要记录上一个结点,遇到空结点了,就让parent左孩子或者右孩子指向新插入的结点。然后结束递归
if (cur == NULL){TreeNode* node = new TreeNode(val);if (parent->val > val) parent->right = node;else parent->left = node;return;
}
  • 确定单层递归逻辑
//让某结点一直跟在cur结点的屁股后面的常用方法
parent = cur;
if (cur->val > val) traversal(cur->left, val);
if (cur->val < val) traversal(cur->right, val);
return;

迭代方法

关于BST的迭代方法其实普遍都比较简单,因为二叉搜索树毕竟是有序的,遍历方向比较好控制。

迭代方法的基本逻辑就是:

  • 如果root为空,我们需要处理
if (root == nullptr){TreeNode* node = new TreeNode(val);return node;
}
  • 定义一个父结点parent,他是仅仅更在cur后面的结点,后续我们需要它来进行赋值操作
TreeNode* cur = root;
TreeNode* parent = root;
  • 通过迭代方法找到插入点的位置
while (cur != NULL){parent = cur;if (cur->val > val) cur = cur->left;if (cur->val > val) cur = cur->right;
}
  • 利用定义的parent进行赋值
//等我们跳出循环,parent就在插入位置的父结点位置
if (parent->val > val) parent->left = node;
else parent->right = node;
return root;

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

递归无返回值

class Solution {
private:TreeNode* parent;void traversal(TreeNode* cur, int val) {if (cur == NULL) {TreeNode* node = new TreeNode(val);if (val > parent->val) parent->right = node;else parent->left = node;return;}parent = cur;if (cur->val > val) traversal(cur->left, val);if (cur->val < val) traversal(cur->right, val);return;}public:TreeNode* insertIntoBST(TreeNode* root, int val) {parent = NULL;//把结点初始化一下子if (root == NULL) {root = new TreeNode(val);}traversal(root, val);return root;}
};

迭代

class Solution {
public:TreeNode* insertIntoBST(TreeNode* root, int val) {if (root == NULL) {TreeNode* node = new TreeNode(val);return node;}TreeNode* cur = root;TreeNode* parent = root; // 这个很重要,需要记录上一个节点,否则无法赋值新节点while (cur != NULL) {parent = cur;if (cur->val > val) cur = cur->left;else cur = cur->right;}TreeNode* node = new TreeNode(val);if (val < parent->val) parent->left = node;// 此时是用parent节点的进行赋值else parent->right = node;return root;}
};

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

力扣题目链接

文章讲解:450.删除二叉搜索树中的结点

视频讲解:调整二叉树的结构最难!| LeetCode:450.删除二叉搜索树中的节点

状态:由于删除二叉搜索树可能涉及到二叉搜索树结构的改变,所以一定要注意分情况讨论,详细的代码实现也一定要记住。

思路(分析五种情况)

  1. 首先要注意,二叉树是链式存储的,所以我们的删除不是真的删除,而是父结点指向新的孩子

  2. 本题相对于之前添加结点的操作,本题要难很多。因为删除结点我们必须要改变二叉树的结构

没找到删除的点

遍历到空结点直接返回

if (root == nullptr) return root;

删的点是叶子结点

左右孩子都为空,直接删除结点,返回NULL为根结点

if (root->left == nullptr && root->right == nullptr){//内存释放delete root;return nullptr;
}

要删的结点左为空,右为空

其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点

else if (root->left == nullptr){auto retNode = root->left;delete root;return retNode;
}

要删的结点左不为空,右为空

其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点

else if (root->right == nullptr){auto retNode = root->left;delete root;return reNode;
}

要删的结点左右都不为空

这里就讲究了,涉及到插入结点的操作,因为我们可以让待删结点的右孩子来代替删除位置,那么待删结点的左孩子就必须连根带叶得插入到右孩子,这里我们继续延续701.二叉搜索树中的插入操作中的思想,直接作为右孩子的叶子即可

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的右孩子作为新rootdelete tmp;	//释放结点内存return root;
}

伪代码实现

  • 确定递归函数的返回值和参数:返回值就是新结点的根结点;然后root是带删结点,key就是要删除的值
TreeNode* delete(root, key){}
//该函数就是leetCode提供的主函数
  • 确定递归的终止条件:

    • 在本题中,我们不是要遍历整颗二叉树才开始终止,其实只要找到了我们要删除的点就得删。那么既然我们找到了要删除的点了,所以删除逻辑也要写出来
    //没找到要删除的结点
    if (root == NULL) return NULL;
    if (root->val == key){if (root->left == NULL && root->right == NULL)return NULL;//这里的NULL return到哪,其实是返回到被删叶子结点的父结点了else if (root->left != NULL && root->right == NULL) return root->left;//让待删结点的左子树直接返回到待删结点的父结点那儿,完成待删结点的移除else if (root->left == NULL && root->right != NULL)return root->rightl;else{ //先找到待删结点最左侧的值代替位置cur = root->right;while(cur->left != NULL) cur = cur->left;//现在cur指向了右孩子的最左叶子cur->left = root->left; //完成待删结点的左子树连入右子树的叶子return root->right; //真正删除待删结点}
    }
    
  • 单层递归逻辑

//这里的root->left与上文的代码对应,就是接住被删结点的孩子。这里的逻辑非常重要!
if (key < root->val) root->left = delete(root->left, key);
if (key > root->val) root->right = delete(root->right, key);
return root;

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的右孩子作为新rootdelete 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;}
};

二叉搜索树的迭代法删除结点

class Solution {
private:// 将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上// 并返回目标节点右孩子为新的根节点// 是动画里模拟的过程TreeNode* deleteOneNode(TreeNode* target) {if (target == nullptr) return target;if (target->right == nullptr) return target->left;TreeNode* cur = target->right;while (cur->left) {cur = cur->left;}cur->left = target->left;return target->right;}
public:TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) return root;TreeNode* cur = root;TreeNode* pre = nullptr; // 记录cur的父节点,用来删除curwhile (cur) {if (cur->val == key) break;pre = cur;if (cur->val > key) cur = cur->left;else cur = cur->right;}if (pre == nullptr) { // 如果搜索树只有头结点return deleteOneNode(cur);}// pre 要知道是删左孩子还是右孩子if (pre->left && pre->left->val == key) {pre->left = deleteOneNode(cur);}if (pre->right && pre->right->val == key) {pre->right = deleteOneNode(cur);}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;}
};

这篇关于代码随想录算法训练营DAY22|C++二叉树Part.8|235.二叉搜索树的最近公共祖先、450.删除二叉搜索树中的结点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/905276

相关文章

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Git中恢复已删除分支的几种方法

《Git中恢复已删除分支的几种方法》:本文主要介绍在Git中恢复已删除分支的几种方法,包括查找提交记录、恢复分支、推送恢复的分支等步骤,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录1. 恢复本地删除的分支场景方法2. 恢复远程删除的分支场景方法3. 恢复未推送的本地删除分支场景方法4. 恢复

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

Redis过期键删除策略解读

《Redis过期键删除策略解读》Redis通过惰性删除策略和定期删除策略来管理过期键,惰性删除策略在键被访问时检查是否过期并删除,节省CPU开销但可能导致过期键滞留,定期删除策略定期扫描并删除过期键,... 目录1.Redis使用两种不同的策略来删除过期键,分别是惰性删除策略和定期删除策略1.1惰性删除策略