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

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�