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

news2024/11/25 4:32:27

第二十天,二叉树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.coloradmin.cn/o/1862003.html

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

相关文章

在FlowUs息流,让知识库为你所用|如何打造个人知识库|如何打造企业知识库

&#x1f389; 在 FlowUs 的世界中&#xff0c;知识绽放出无限的可能&#xff01;&#x1f680; 在当今信息爆炸的时代&#xff0c;知识的更新换代速度极快&#xff0c;我们每天都面临着海量的信息冲击。拥有一个属于自己的知识库变得至关重要。 首先&#xff0c;打造自己的知…

Linux_应用篇(27) CMake 入门与进阶

在前面章节内容中&#xff0c;我们编写了很多示例程序&#xff0c;但这些示例程序都只有一个.c 源文件&#xff0c;非常简单。 所以&#xff0c;编译这些示例代码其实都非常简单&#xff0c;直接使用 GCC 编译器编译即可&#xff0c;连 Makefile 都不需要。但是&#xff0c;在实…

解决IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题

前言 本篇文章主要是记录解决百问网论坛上面设置 IMX6ULL GPIO扩展板PWM7/8中的pwm0/period后卡死问题&#xff0c;如下图&#xff1a; 一、查看原理图&#xff0c;找出对应引脚 在这里我们如何确定哪个扩展口中的引脚输出PWM波呢&#xff1f;我们可以通过查看原理图。 查看…

【Java】解决Java报错:IllegalMonitorStateException in Synchronization

文章目录 引言一、IllegalMonitorStateException的定义与概述1. 什么是IllegalMonitorStateException&#xff1f;2. IllegalMonitorStateException的常见触发场景3. 示例代码 二、解决方案1. 确保在同步代码块或方法中调用wait()、notify()和notifyAll()2. 使用同步方法3. 使用…

办公人导航-上网导航,找网站,下软件,找资源!

办公人导航是一个专门为办公人员设计的实用导航网站&#xff0c;旨在帮助用户高效地找到各种优质的办公资源和工具。无论是需要查找办公软件、学习资源还是娱乐工具&#xff0c;在办公人导航上都能找到你需要的内容。 办公人导航-实用的办公生活导航网站&#xff01;https://ww…

Linux内核测试技术

Linux 内核是Linux操作系统的核心部分&#xff0c;负责管理硬件资源和提供系统调用接口。随着 Linux 内核的不断发展和更新&#xff0c;其复杂性和代码规模也在不断增加。因此&#xff0c;确保内核的稳定性和可靠性变得尤为重要。内核测试技术是实现这一目标的关键手段。本文将…

Java使用Graphics2D画图,画圆,矩形,透明度等实现

背景 如上图,需要使用Java生成一个图片, 并以base64编码的形式返回给前端展示。 使用Graphics2D类,来进行画图,其中需要画方框、原型、插入图标、写入文字等,同时需要设置透明度等细节点 环境:Jdk17,springboot2.7.13 代码如下 有详细的注释 package com.demo;import c…

白帽子的海外第一单,750刀

国际惯例&#xff0c;给兄弟们看图 这是我们师傅挖国外SRC的部分赏金截图 就问你&#xff01;挖国外漏洞赚美金香不香&#xff01; 现在国内SRC越来越卷了&#xff0c;越来越多的白帽子开始挖海外漏洞赚美金。海外SRC真的比国内赏金高很多&#xff0c;不说高危漏洞&#xff0…

自编码器笔记

编码器解码器自编码器 先压缩特征&#xff0c;再通过特征还原。 判断还原的和原来的是否相等 encode data 在一个“潜在空间”里。它的用途是“深度学习”的核心-学习数据的特征并简化数据表示形式以寻找模式。 变分自编码器&#xff1a; 1. 首先、假设输入数据是符合正态分布…

《mnist_model.h5》在flask中加载mnist模型

一、在tensorflow中新建及保存模型 启动Jupyter Notebook 新建Notebook 代码 from flask import Flask, request, jsonify # type: ignore import numpy as np # type: ignore import tensorflow as tf # type: ignore import json from PIL import Image # type: i…

bigtop gradle 任务依赖关系

./gradlew deb 会编译ubuntu的所有deb包 任务deb会依赖17个任务&#xff0c;它们会按字母排序执行&#xff0c;如下&#xff1a; alluxio-deb bigtop-groovy-deb bigtop-jsvc-deb bigtop-utils-deb flink-deb hadoop-deb hbase-deb hive-deb kafka-deb livy-deb phoenix-deb …

React 19 新特性集合

前言&#xff1a;https://juejin.cn/post/7337207433868197915 新 React 版本信息 伴随 React v19 Beta 的发布&#xff0c;React v18.3 也一并发布。 React v18.3相比最后一个 React v18 的版本 v18.2 &#xff0c;v18.3 添加了一些警告提示&#xff0c;便于尽早发现问题&a…

51单片机STC89C52RC——8.1 8*8 LED点阵模块(点亮一个LED)

目录 目的/效果 一&#xff0c;STC单片机模块 二&#xff0c;8*8 LED点阵模块 2.1 电路图 2.1.1 8*8 点阵模块电路图 2.1.2 74HC595&#xff08;串转并&#xff09;模块 电路图 2.1.3 芯片引脚 2.2 引脚电平分析 2.3 74HC595 串转并模块 2.3.1 装弹&#xff08;移位…

强化学习专题:强化学习知识梳理(一)

2024/6/23&#xff1a; 前段时间有幸完成了大学期间的第一篇论文。在面试之前复盘一下关于自己论文中DQN的一些相关点。 浅谈主要区别&#xff08;在线 or 离线&#xff09; 首先&#xff0c;一切的开始是强化学习中时序差分方程&#xff0c;这体现了强化学习方法的优化策略。在…

【MySQL进阶之路 | 高级篇】MySQL8.0索引新特性->降序索引与隐藏索引

1. 支持降序索引 降序索引以降序存储键值.虽然在语法上&#xff0c;从MySQL4版本已经支持降序索引的语法了&#xff0c;但实际上该DESC定义是被忽略的.知道MySQL8.x版本才开始真正支持降序索引.(仅限于InnoDB存储引擎). MySQL在8.0版本前创建的仍然是升序索引&#xff0c;使用…

PADS系列:如何导入元件库新建元件

对于普通的原理图&#xff0c;位置的摆放是比较随意的&#xff0c;并且也没有一些特殊的或者元件库里面没有的元件&#xff0c;相对来说绘制会比较简单。但是如果碰上复杂一点的电路&#xff0c;要绘制起来就会比较麻烦&#xff0c;需要一些新的PADS使用技巧&#xff0c;最基础…

云计算考试题

Cloud ❀ 云计算-虚拟化常见的两种架构_裸金属架构和宿主型架构的区别-CSDN博客 为啥要成2 11 bcd 16 acd abcd BCD NAS为啥支持文件存储的协议 选BCD 什么是网络文件系统 选bcd 错题 选abc 选bcd 选 abd

NetSuite Account Merge 科目合并功能分析

最近项目中&#xff0c;客户有提到过能否将不用的Account与新建的Account进行合并&#xff0c;即我们所说的Merge功能&#xff5e;可以&#xff0c;但是该功能有使用的限制&#xff0c;比如最直接的一点需要注意&#xff0c;不同类型的Account是不可以使用Merge功能的&#xff…

c++内存管理_复习

new与placement new new&#xff1a; 先调用operator new(大小)&#xff0c;而operator new()会调用malloc尝试分配内存&#xff0c;失败则调用_callnewh()来释放内存&#xff0c;直至分配成功 可以设置分配失败的处理函数&#xff1a;将写好的处理函数作为参数传入set_new_han…

一文2000字记录基于jmeter+perfmon的稳定性测试

01、任务情况 1、任务总览 本次平台稳定性测试的目的在于&#xff1a;在服务器压力处于较饱和&#xff08;达到80%系统最大TPS&#xff09;压力之下&#xff0c;在较长时间&#xff08;>8小时&#xff09;之内观测服务器稳定性问题&#xff0c;以及资源使用情况和异常。 …