第二十天|二叉搜索树的公共祖先,修改与构造| 235. 二叉搜索树的最近公共祖先, 701. 二叉搜索树中的插入操作,450. 删除二叉搜索树中的节点

news2024/10/8 17:52:31

关于二叉搜索树的题目,貌似普遍用迭代法比递归法简单。目前做到的除了98验证二叉搜索树都是如此。

701其实很简单,只是之前自己想不到直接添加到叶子节点这个方法。

注意一个问题:判断需要返回 root 还是 newRoot

  • 返回 root:当操作不改变树的根节点时,返回 root。例如,在插入或删除时,根节点没有被替换,树的结构依然连贯。
  • 返回 newRoot:当操作导致树的根节点发生变化时,返回 newRoot。这通常发生在根节点为空(即空树)或删除操作导致根节点被替换的情况下。

判断是否需要返回 root 还是 newRoot 的关键是看操作是否会改变树的根节点结构。如果根节点没有发生变化,通常返回 root 即可;否则,返回新的根节点。

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

因为刚做了236. 二叉树的最近公共祖先,一看到这个题目的时候思考方式还是从底向上遍历,又想利用二叉搜索树的性质,毫无头绪,没有想法。看了题解才觉得这道题的想法这么妙,就很简单。

总结

  • 对于二叉搜索树的最近祖先问题,其实要比普通二叉树的最近公共祖先问题简单的多。
  • 不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
  • 最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。

思路:

  • 不需要遍历整棵树,找到结果直接返回!
  • 因为二叉搜索树是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。

注意:如何保证该节点就是最近公共祖先呢?

        从根节点搜索,第一次遇到 cur节点是数值在[q, p]区间中,即 节点5,此时可以说明 q 和 p 一定分别存在于 节点 5的左子树,和右子树中。

        此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为p的祖先, 如果从节点5继续向右遍历则错过成为q的祖先。

        所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[q, p]区间中,那么cur就是 q和p的最近公共祖先。

递归法

递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 递归法
        // 如果 cur->val 大于 p->val,同时 cur->val 大于 q->val,那么就应该向左遍历(目标区间在左子树)。
        if (root.val > p.val && root.val > q.val)
            return lowestCommonAncestor(root.left,p,q);
        // 如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)。
        if (root.val < p.val && root.val < q.val)
            return lowestCommonAncestor(root.right,p,q);
        // 剩下的情况,就是cur节点在区间[p,q]或者[q,p]
        // 那么root就是最近公共祖先了,直接返回root
        return root;
    }
}

235和236递归函数返回值的区别

如果递归函数有返回值,如何区分要搜索一条边(235),还是搜索整个树(236)。

搜索一条边的写法:

if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

搜索整个树写法:

left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;

235就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。

迭代法(简单)

利用其有序性,迭代的方式还是比较简单的,解题思路在递归中已经分析了。

    class Solution {
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            // 迭代法
            while (root != null) {
                if (root.val > p.val && root.val > q.val)
                    root = root.left;
                else if (root.val < p.val && root.val < q.val)
                    root = root.right;
                else
                    return root;
            }
            return null;
        }
    }

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

本题一开始看到没有思路。当不考虑题目中提示所说的改变树的结构的插入方式,直接插入到叶子节点就简单了。在二叉搜索树中的插入操作,其实根本不用重构搜索树。

只要按照二叉搜索树的规则去遍历,遇到空节点(末尾节点)就插入节点就可以了。

递归法(看这个)

搜索树是有方向的,可以根据插入元素的数值,决定递归方向。

遍历整棵搜索树简直是对搜索树的侮辱。

不需遍历整棵树!

   class Solution {
        public TreeNode insertIntoBST(TreeNode root, int val) {
            // 递归法
            // 如果当前节点为空,也就意味着val找到了合适的位置,此时创建节点直接返回。
            if (root == null)
                return new TreeNode(val);
            if (root.val < val) {
                root.right = insertIntoBST(root.right, val);// 递归创建右子树
            } else if (root.val > val) {
                root.left = insertIntoBST(root.left, val);// 递归创建左子树
            }
            return root;
        }
    }

迭代法(供参考)

在迭代法遍历的过程中,需要记录一下当前遍历的节点的父节点,这样才能做插入节点的操作。

用记录pre和cur两个指针的技巧。

  • 代码的整体思路是使用一个指针root遍历树,寻找合适的插入位置。遍历过程中通过pre记录父节点,最终在树的叶节点处插入新的值。
  • 最后返回的是最初保存的根节点newRoot,因为二叉搜索树的插入操作不会改变根节点的位置。
    class Solution {
        public TreeNode insertIntoBST(TreeNode root, int val) {
            // 迭代法
            if (root == null) return new TreeNode(val);
            TreeNode newRoot = root; // 保存初始根节点
            TreeNode pre = root; // pre用于保存root的前一个节点,即插入过程中最后一次遍历到的父节点。因为当root遍历到null时,需要通过pre来插入新节点到合适的位置。
            while (root != null) {
                pre = root; // 每次移动时,将pre更新为当前节点
                if (root.val > val) {
                    root = root.left;
                } else if (root.val < val) {
                    root = root.right;
                }
            }
            // 插入新节点
            if (pre.val > val)
                pre.left = new TreeNode(val);
            else
                pre.right = new TreeNode(val);
            return newRoot; // 返回初始的根节点
        }
    }

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

递归法1(看这个)

二叉搜索树中删除节点有以下五种情况:

  • 第一种情况:没找到删除的节点,遍历到空节点直接返回了
  • 找到删除的节点
    • 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
    • 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    • 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    • 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点(此时相当于左空右不空,情况三)

这里用父节点直接接收返回值。很妙,不需要单独定义一个父节点。(把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住)具体见下面代码。

if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);

整体代码如下:

    class Solution {
        public TreeNode deleteNode(TreeNode root, int key) {
            // 递归法
            if (root == null) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
            if (root.val == key) {
                if (root.left == null) { // 第二三种情况,左右都为空(叶子节点),左空右不空
                    return root.right;
                } else if (root.right == null) { // 第四种情况,左不空右空
                    return root.left;
                } else { // 第五种情况,左不空右不空
                    TreeNode cur = root.right;
                    while (cur.left != null)
                        cur = cur.left; // 找到右子树中的最左边的节点
                    cur.left = root.left; // 将要删除节点root的左子树放到cur的左子树下
                    // 此时要删除的节点相当于左空右不空,第三种情况
                    return root.right;
                }
            }
            if (root.val > key) root.left = deleteNode(root.left, key);
            if (root.val < key) root.right = deleteNode(root.right, key);
            return root;
        }
    }

普通二叉树的删除方式

普通二叉树的删除方式(没有使用搜索树的特性,遍历整棵树),用交换值的操作来删除目标节点。

代码中目标节点(要删除的节点)被操作了两次:

  • 第一次是和目标节点的右子树最左面节点交换。
  • 第二次直接被NULL覆盖了。

(我没理解,如果是普通二叉树,为什么要用右子树最左面节点交换,普通二叉树没有排序呀)

递归法2:

对每个节点进行递归处理,寻找需要删除的节点,然后根据节点的不同情况(无子节点、一个子节点、两个子节点)执行相应的删除操作。

   class Solution {
        public TreeNode deleteNode(TreeNode root, int key) {
            root = delete(root, key);
            return root;
        }

        private TreeNode delete(TreeNode root, int key) {
            if (root == null) return null;  // 没有找到要删除的节点
            if (root.val > key) {
                root.left = delete(root.left, key);  // 递归左右子树
            } else if (root.val < key) {
                root.right = delete(root.right, key);  // 递归左右子树
            } else {
                if (root.left == null) return root.right;
                if (root.right == null) return root.left;
                // 处理有两个子节点的情况
                // 用右子树中最小的节点(即右子树的最左边的节点)替换当前节点的值。
                TreeNode tmp = root.right;
                while (tmp.left != null)
                    tmp = tmp.left;
                root.val = tmp.val;
                root.right = delete(root.right, tmp.val); // 再递归地删除右子树中的这个最小节点
            }
            return root;
        }
    }

迭代法

迭代法,就是模拟递归法中的逻辑来删除节点,但需要一个pre记录cur的父节点,方便做删除操作。(迭代法较难)麻烦

    class Solution {
        public TreeNode deleteNode(TreeNode root, int key) {
            // 迭代法,需要pre记录父节点,麻烦
            if (root == null) return null;
            //寻找对应的对应的前面的节点,以及他的前一个节点
            //查找待删除节点
            TreeNode cur = root;
            TreeNode pre = null;
            while (cur != null) {
                if (cur.val < key) {
                    pre = cur;
                    cur = cur.right;
                } else if (cur.val > key) {
                    pre = cur;
                    cur = cur.left;
                } else {
                    break;
                }
            }
            // 特殊情况处理:根节点就是目标节点
            if (pre == null)
                return deleteOneNode(cur);
            // 处理目标节点是父节点的左子节点或右子节点
            if (pre.left != null && pre.left.val == key)
                pre.left = deleteOneNode(cur); // 删除节点,并更新父节点的 left
            if (pre.right != null && pre.right.val == key)
                pre.right = deleteOneNode(cur);
            return root;
        }

        // 删除当前的节点,并处理它的子树连接
        public TreeNode deleteOneNode(TreeNode node) {
            if (node == null)
                return null;
            if (node.right == null)
                return node.left;
            TreeNode cur = node.right;
            while (cur.left != null)
                cur = cur.left;
            cur.left = node.left;  // 一旦找到这个最小节点,把node的左子树连接到 这个右子树最小的左子树节点上
            return node.right; // 返回右子树作为删除节点后的新的根节点。
        }
    }

第二十天的总算是结束了,直冲Day21!

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

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

相关文章

超好用的数据库连接工具-DBeaver连接ClickHouse后找不到系统表?

一、前言 公司内部禁止使用Navicat&#xff0c;又不想装JetBrains的DataGrip。找了半天找到这款完全开源的数据库连接工具&#xff0c;几乎可以连接市面上所有的数据库&#xff0c;功能非常强大 二、工具简介 对关系数据库的基本支持&#xff1a;MySQL、SQL Server、PostgreS…

双十一好物清单!这5款高端又实用的双十一好物千万别错过!

随着双十一购物狂欢节的临近&#xff0c;空气中开始弥漫着一股热烈而兴奋的购物气氛。在这个日子里&#xff0c;商品的折扣与优惠的都比较大&#xff0c;很多人都想挑选一款产品&#xff0c;但是&#xff0c;面对琳琅满目的好物&#xff0c;如何选择变成了一个难题&#xff0c;…

数组综合应用(下标计数)C++

第1题 铅笔 时限&#xff1a;1s 空间&#xff1a;256m 桌面有n个盒子&#xff0c;第i个盒子有a[i]支铅笔。 你想要得到尽量多的铅笔&#xff0c;但是如果某两个盒子有相同数量的铅笔&#xff0c;那么你是不能同时拥有这两个盒子的。 问你最多可以得到多少支铅笔。 输入…

Css flex布局下 两端对齐与居中对齐

两端对齐 <view class"top"><view class"history"><image src"../../static/avatar/history.png" mode"" style"width: 70rpx;height: 70rpx;;"></image></view><view class"title…

【LeetCode】每日一题 2024_10_8 旅行终点站(哈希)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 国庆结束了 . . . 力扣的每日一题也来到了终点站 题目&#xff1a;旅行终点站 代码与解题思路 func destCity(paths [][]string) string { // 国庆结束&#xff0c;旅途到了终点// 今天这道题算是一个小…

Qt Qml Map-地图绘制点与圆的切线

基于此源码替换 main.qml 文件https://download.csdn.net/download/qq_38159549/89860109https://download.csdn.net/download/qq_38159549/89860109 import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.3 import QtLocation 5.3 import QtPositi…

【Linux】进程间通信——System V消息队列和信号量

一、消息队列 1.1 概念 进程间通信的原理是让不同进程看到同一份资源&#xff0c;资源种类的不同就决定了通信方式的差异。如果用管道通信&#xff0c;则资源是文件缓冲区&#xff1b;如果用共享内存&#xff0c;则资源是内存块 消息队列是由操作系统提供的资源&#xff0c;…

注意!新增一本期刊解除On Hold!仍有37本无法检索慎投!

On Hold期刊 2024年9月29日&#xff0c;小编从WOS数据库查到新增一本ESCI期刊《SOCAR Proceedings》解除On Hold标识 目前共37本期刊被标记&#xff0c;期刊整理如下&#xff0c;请注意避雷&#xff01; 01 解除风险期刊 SOCAR Proceedings • ISSN&#xff1a;2218-6867 •…

RAR解压缩软件的全面指南:压缩、加密、修复功能一应俱全

在日常文件管理中&#xff0c;压缩文件格式与解压缩工具是不可或缺的组成部分。而RAR格式&#xff0c;凭借其高效的压缩率、丰富的功能和灵活的文件管理方式&#xff0c;成为了用户最常使用的压缩格式之一。 作为处理RAR格式的专业工具&#xff0c;RAR解压缩软件具备压缩、加密…

马哥亲讲k8s集群搭建

文章目录 Docker和K8s安装1.docker安装2.安装cri-dockerd3.安装kubelet、kubeadm、kubectl4.整合kubelet和cri-dockerd 集群节点部署1.配置国内镜像站2.方式一&#xff1a;命令初始化1.kubeadm init2.保存初始化token3.拷贝/etc/kubernetes/admin.conf4.部署网络插件5.kubectl …

reactNative本地调试localhost踩坑

本地调试请求localhost的时候 1.要和电脑处在同一局域网下面&#xff08;同一个wifi&#xff09; 2.把baseURL的localhost改成命令行中ipconfig查询到的IPv4 地址 . . . . . . . . . . . . : &#xff08;例如&#xff09;192.168.1.103 如果报错Net Work Error&#xff0c;可…

算法知识点————贪心

贪心&#xff1a;只考虑局部最优解&#xff0c;不考虑全部最优解。有时候得不到最优解。 DP&#xff1a;考虑全局最优解。DP的特点&#xff1a;无后效性&#xff08;正在求解的时候不关心前面的解是怎么求的&#xff09;&#xff1b; 二者都是在求最优解的&#xff0c;都有最优…

如何扫描HTTP代理:步骤与注意事项

HTTP代理是一个复杂的过程&#xff0c;通常用于寻找可用的代理服务器&#xff0c;以便在网络中实现匿名或加速访问。虽然这个过程可以帮助用户找到适合的代理&#xff0c;但也需要注意合法性和道德问题。本文将介绍如何扫描HTTP代理&#xff0c;并提供一些建议和注意事项。 什…

剖析十大经典二叉树题目:C 语言代码实现与深度解读

&#x1f4af;前言 二叉树是数据结构中的重要概念&#xff0c;在算法和编程中有着广泛的应用。以下是十大经典的二叉树题目及其解析与 C 语言代码实现&#xff0c;同时也会说明题目来源。 二叉树的基本概念&#x1f449;【剖析二叉树】 目录 &#x1f4af;二叉树的遍历 ⭐前…

AI大模型真的是大龄程序员的新的出路吗?_大龄程序员ai创业

前言 在IT行业的高速运转中&#xff0c;许多资深程序员到了一定年龄后&#xff0c;会发现自己陷入了职业发展的瓶颈。尤其是在北京这样的大厂&#xff0c;业务波动、部门调整以及裁员风险&#xff0c;都让“40”的程序员们感受到了前所未有的压力。当昔日的技术热情逐渐消退&a…

在C#中使用适配器Adapter模式和扩展方法解决面向对象设计问题

之前有阵子在业余时间拓展自己的一个游戏框架&#xff0c;结果在实现的过程中发现一个设计问题。这个游戏框架基于MonoGame实现&#xff0c;在MonoGame中&#xff0c;所有的材质渲染&#xff08;Texture Rendering&#xff09;都是通过SpriteBatch类来完成的。举个例子&#xf…

新书速览|你好,C++

《你好&#xff0c;C》 本书内容 《你好&#xff0c;C》主要介绍C开发环境的搭建、基础语法知识、面向对象编程思想以及标准模板库的应用&#xff0c;特别针对初学者在学习C过程中可能遇到的难点提供了解决方案。全书共分13章&#xff0c;以一个工资程序的不断优化和完善为线索…

ChatGPT助力文献综述写作:提升效率与写作技巧!

文献综述在论文写作中占有举足轻重的地位。它不仅帮助我们梳理已有的研究成果&#xff0c;还能为自己的研究奠定基础。许多同学在撰写文献综述时常常感到头疼&#xff1a;如何处理海量的信息&#xff1f;如何将不同的观点有条理地整合起来&#xff1f;再加上学术语言的高要求&a…

定时任务。

引入 1.启动类上加上注解 2.新建一个定时任务的管理类&#xff0c;交给Spring管理 案例 案例1&#xff1a;fixedRate //上次任务开始到下次任务开始的时间间隔为5秒 //每隔5秒执行一次,不需要等上个任务执行完 Scheduled(fixedRate 5000) public void mask01() throws Inte…

python:web自动化工具selenium安装和配置(1)

UI自动化测试 UI自动化测试&#xff08;User Interface Automation Testing&#xff09;是一种通过编写脚本或使用自动化测试工具&#xff0c;对界面&#xff08;UI&#xff09;进行自动化测试的方法。原理主要是模拟用户打开客户端或网页的UI界面&#xff0c;自动化执行用户界…