代码随想录算法训练营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

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

docker如何删除悬空镜像

《docker如何删除悬空镜像》文章介绍了如何使用Docker命令删除悬空镜像,以提高服务器空间利用率,通过使用dockerimage命令结合filter和awk工具,可以过滤出没有Tag的镜像,并将... 目录docChina编程ker删除悬空镜像前言悬空镜像docker官方提供的方式自定义方式总结docker

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意