算法日记day 20(二叉搜索树)

news2024/11/16 2:44:04

一、验证二叉搜索树

题目:

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左

    子树

    只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:root = [2,1,3]
输出:true

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

 思路:

采用中序:左、中、右的方法,利用双指针,由于二叉搜索树的中序遍历是一个递增的序列,因此只需要比较相邻两个元素的值是否是符合递增的条件即可

代码:

TreeNode pre = null;

public boolean isValidBST(TreeNode root) {
    if (root == null)
        return true;
    
    // 递归检查左子树
    boolean leftValue = isValidBST(root.left);
    
    // 检查当前节点值是否大于前一个节点值
    //pre != null表示在第一次遍历时只有一个指针的值被确立,只有在第二次遍历时才能记录两个元素并进行比较,因此第一次if判断不进行
    if (pre != null && pre.val >= root.val) {
        return false;
    }
    
    // 更新 pre 为当前节点
    pre = root;
    
    // 递归检查右子树
    boolean rightValue = isValidBST(root.right);
    
    // 返回左右子树是否都是 BST
    return leftValue && rightValue;
}
  1. 变量 pre 的作用

    • pre 是一个全局变量,用来保存当前节点在中序遍历中的前一个节点。在中序遍历的过程中,通过比较当前节点与前一个节点的值来判断是否满足 BST 的性质(左子树的所有节点值均小于当前节点,右子树的所有节点值均大于当前节点)。
  2. isValidBST 方法

    • 方法签名为 boolean isValidBST(TreeNode root),返回一个布尔值,表示以 root 为根的子树是否是 BST。
    • 如果 root 为 null,则空树被视为 BST,直接返回 true
  3. 递归检查左子树

    • boolean leftValue = isValidBST(root.left); 递归调用 isValidBST 方法,检查左子树是否为 BST,并将结果保存在 leftValue 中。
  4. 检查当前节点值

    • if (pre != null && pre.val >= root.val) 检查当前节点 root 的值与 pre 的值(前一个节点的值)比较,如果不满足 BST 的性质(即当前节点值小于等于前一个节点的值),则返回 false
  5. 更新 pre

    • pre = root; 将 pre 更新为当前节点 root,以便在下一次递归中使用。
  6. 递归检查右子树

    • boolean rightValue = isValidBST(root.right); 递归调用 isValidBST 方法,检查右子树是否为 BST,并将结果保存在 rightValue 中。
  7. 返回结果

    • return leftValue && rightValue; 将左子树和右子树是否都为 BST 的结果进行逻辑与操作,最终确定以 root 为根的子树是否是 BST。

二、 二叉搜索树最小绝对差

题目:

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

输入:root = [4,2,6,1,3]
输出:1

示例 2:

输入:root = [1,0,48,null,null,12,49]
输出:1

思路:

与验证二叉搜索树类似,仍是采用中序遍历的方法,将节点的值按递增排序出来后相邻两值相减,记录最小的绝对差

代码:

int result = Integer.MAX_VALUE; // 初始化结果为整型最大值,用于保存最小差值
TreeNode pre = null; // 用来保存中序遍历中当前节点的前一个节点

/**
 * 计算二叉搜索树中任意两个节点值的最小差值
 * @param root 当前子树的根节点
 * @return 最小差值
 */
public int getMinimumDifference(TreeNode root) {
    if (root == null)
        return 0; // 如果根节点为空,返回 0,因为空树不存在差值

    // 递归处理左子树
    getMinimumDifference(root.left);

    // 计算当前节点与前一个节点的差值,并更新结果
    if (pre != null) {
        result = Math.min(result, root.val - pre.val);
    }

    // 更新前一个节点为当前节点
    pre = root;

    // 递归处理右子树
    getMinimumDifference(root.right);

    // 返回最小差值
    return result;
}
  1. 全局变量

    • result 初始化为整型最大值,用来存储找到的最小节点值差。
    • pre 用来跟踪中序遍历中的前一个节点。
  2. 方法 getMinimumDifference

    • 该方法计算给定二叉搜索树中任意两个节点值的最小差值。
    • root 参数表示当前子树的根节点。
  3. 空节点处理

    • 如果 root 为 null,表示当前子树为空树,直接返回 0,因为空树不存在节点差值。
  4. 递归左子树

    • getMinimumDifference(root.left); 递归调用,处理左子树。
  5. 计算节点差值

    • if (pre != null) { result = Math.min(result, root.val - pre.val); }
      • 如果 pre 不为 null,说明当前节点 root 不是最左侧节点,计算当前节点值与前一个节点值的差,并更新 result 为这两者之差的最小值。
  6. 更新前一个节点

    • pre = root; 更新 pre 为当前节点 root,为下一次递归调用做准备。
  7. 递归右子树

    • getMinimumDifference(root.right); 递归处理右子树。
  8. 返回结果

    • return result; 返回计算得到的最小差值。

 

三、 求二叉搜索树中的众数

题目:

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

输入:root = [1,null,2,2]
输出:[2]

示例 2:

输入:root = [0]
输出:[0]

思路:

用双指针的方法,在中序遍历得出的递增数组中找出出现频率最大的数,存入结果数组中,如果在遍历的过程中找到出现频率更大的数,则需要将结果数组中存入的数全部清空,同时将频率更高的数加入结果数组,直到遍历完毕

代码:

public void find(TreeNode root) {
    if (root == null)
        return; // 如果当前节点为空,直接返回

    find(root.left); // 递归处理左子树

    // 计数逻辑
    if (pre == null || root.val != pre.val) {
        count = 1; // 如果当前节点与前一个节点不同,重置计数器为1
    } else {
        count++; // 如果当前节点与前一个节点相同,计数器加1
    }

    // 更新众数列表和最大计数值
    if (count > maxCount) {
        resList.clear(); // 如果当前计数大于最大计数,清空之前的结果列表
        resList.add(root.val); // 将当前节点值加入结果列表
        maxCount = count; // 更新最大计数值
    } else if (count == maxCount) {
        resList.add(root.val); // 如果当前计数等于最大计数,将当前节点值加入结果列表
    }

    pre = root; // 更新前一个节点为当前节点

    find(root.right); // 递归处理右子树
}
  • find 方法是核心的递归方法,用于执行中序遍历并计算节点值的出现次数。
  • 如果 root 为 null,直接返回,结束递归。
  • 递归地处理左子树。
  • 根据中序遍历的顺序,判断当前节点值与前一个节点值是否相同,更新计数器 count
  • 根据计数器 count 的值更新 resList 中的节点值和 maxCount
  • 更新 pre 为当前节点,为下一次递归调用做准备。
  • 递归地处理右子树。
public int[] findMode(TreeNode root) {
    find(root); // 调用递归方法进行中序遍历
    int[] res = new int[resList.size()]; // 创建结果数组
    for (int i = 0; i < resList.size(); i++) {
        res[i] = resList.get(i); // 将结果列表中的值复制到结果数组
    }
    return res; // 返回结果数组
}
  • TreeNode pre = null;:用于保存中序遍历中的前一个节点。
  • int maxCount = 0;:记录出现最多次数的节点值的出现次数。
  • int count = 0;:当前节点值的出现次数。
  • ArrayList<Integer> resList = new ArrayList<>();:用于存储找到的所有众数的列表。
  • 首先调用 find(root) 方法来进行中序遍历,并找到众数。
  • 创建一个数组 res,将 resList 中的值复制到 res 中,并返回。

 

四、二叉树的最近公共祖先 

题目:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例 2:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。
因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

思路:

采用后序遍历,当左子树和右子树存在p和q时,返回其父节点为最近公共祖先,若左子树没有,右子树存在p和q,返回右子树节点,同理若右子树没有,左子树存在,返回左子树节点

代码:

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    // 如果当前节点为空,返回null,表示未找到最近公共祖先
    if (root == null)
        return null;
    
    // 如果当前节点是p或q,说明当前节点就是其中一个目标节点的最近公共祖先
    if (root == p || root == q)
        return root;
    
    // 递归查找左子树中的最近公共祖先
    TreeNode leftValue = lowestCommonAncestor(root.left, p, q);
    
    // 递归查找右子树中的最近公共祖先
    TreeNode rightValue = lowestCommonAncestor(root.right, p, q);
    
    // 左右子树分别找到了p和q的最近公共祖先
    if (leftValue != null && rightValue != null)
        return root; // 当前节点即为最近公共祖先
    
    // 只有右子树找到了最近公共祖先
    if (leftValue == null && rightValue != null) {
        return rightValue;
    } else if (leftValue != null && rightValue == null) { // 只有左子树找到了最近公共祖先
        return leftValue;
    } else {
        return null; // 左右子树均未找到最近公共祖先
    }
}
  • 如果当前节点 root 为空,直接返回 null,表示未找到最近公共祖先。
  • 如果当前节点 root 等于 p 或者 q,说明当前节点就是其中一个目标节点,直接返回当前节点 root
  • 如果左子树找到了 p 和 q 的最近公共祖先 leftValue,右子树也找到了 p 和 q 的最近公共祖先 rightValue,那么当前节点 root 就是它们的最近公共祖先,直接返回 root
  • 如果只有左子树找到了最近公共祖先 leftValue,则返回 leftValue
  • 如果只有右子树找到了最近公共祖先 rightValue,则返回 rightValue
  • 如果左右子树都没有找到,则返回 null,表示在当前子树中不存在 p 和 q 的最近公共祖先。

五、二叉搜索树的最近公共祖先

题目: 

给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2和节点 4的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

思路:

利用二叉搜索树的性质,如果p和q的值小于根节点的值,则说明其最近公共祖先在左子树中,反之如果p和q的值大于根节点的值,则说明其最近公共祖先在右子树中,在遍历过程中如果节点的值大于p且小于p,说明该节点为最近公共祖先

代码:

//递归法
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null)
        return null;
    
    // 如果当前节点的值大于p和q的值,则它们的最近公共祖先一定在左子树中
    if (root.val > p.val && root.val > q.val) {
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        if (left != null) {
            return left; // 如果左子树递归找到了最近公共祖先,直接返回
        }
    } 
    // 如果当前节点的值小于p和q的值,则它们的最近公共祖先一定在右子树中
    else if (root.val < p.val && root.val < q.val) {
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (right != null) {
            return right; // 如果右子树递归找到了最近公共祖先,直接返回
        }
    }
    
    // 如果当前节点的值介于p和q的值之间,或者当前节点就是p或q,则当前节点就是最近公共祖先
    return root;
}
  • 如果当前节点的值大于 p 和 q 的值,则说明 p 和 q 都在当前节点的左子树中,递归地在左子树中查找最近公共祖先。
  • 如果当前节点的值小于 p 和 q 的值,则说明 p 和 q 都在当前节点的右子树中,递归地在右子树中查找最近公共祖先。
//迭代法
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    // 迭代查找最近公共祖先
    while (root != null) {
        if (root.val < p.val && root.val < q.val) {
            // 如果根节点的值都小于p和q的值,说明p和q都在右子树
            root = root.right; // 移动到右子树继续查找
        } else if (root.val > p.val && root.val > q.val) {
            // 如果根节点的值都大于p和q的值,说明p和q都在左子树
            root = root.left; // 移动到左子树继续查找
        } else {
            // 如果根节点的值介于p和q的值之间,或者根节点就是p或q,返回根节点
            return root; // 找到最近公共祖先,返回
        }
    }

    // 如果未找到最近公共祖先,返回null
    return null;
}
  • 利用 while 循环,只要当前节点 root 不为空,就进行迭代处理。
  • 在每一轮迭代中,根据 root 的值和 p, q的值的关系来决定向左子树或右子树移动:
    • 如果 root.val 小于 p.val 和 q.val,说明 p 和 q 都在 root 的右子树中,因此将 root 移动到右子树 root.right
    • 如果 root.val 大于 p.val 和 q.val,说明 p 和 q 都在 root 的左子树中,因此将 root 移动到左子树 root.left
    • 如果 root.val 介于 p.val 和 q.val 之间,或者等于其中之一的值,说明 root 就是 p 和 q 的最近公共祖先,直接返回 root。

今天的学习就到这里 

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

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

相关文章

学习记录——day20 IO

IO基础 1、IO&#xff1a;&#xff08;inout output&#xff09; 程序与外部设备进行信息交换的过程 2、IO的分类&#xff1a;标准IO和文件IO 1&#xff09;标准IO&#xff1a;调用封装好的相关库函数&#xff0c;来实现数据的输入输出 2&#xff09;文件IO&#xff1a; 3、…

vue3响应式用法(高阶性能优化)

文章目录 前言&#xff1a;一、 shallowRef()二、 triggerRef()三、 customRef()四、 shallowReactive()五、 toRaw()六、 markRaw()七、 shallowReadonly()小结&#xff1a; 前言&#xff1a; 翻别人代码时&#xff0c;总结发现极大部分使用vue3的人只会用ref和reactive处理响…

废品回收小程序制作,数字化带来的商业机会

随着社会环保意识的增强&#xff0c;废品回收成为了一个热门行业&#xff0c;它不仅能够减少资源浪费&#xff0c;还能够带来新的商业机会&#xff01; 当下&#xff0c;废品回收小程序已经成为了回收市场的重要方式&#xff0c;为回收行业的发展注入新鲜活力&#xff0c;推动…

如何使用C#快速创建定时任务

原文链接&#xff1a;https://www.cnblogs.com/zhaotianff/p/17511040.html 使用Windows的计划任务功能可以创建定时任务。 使用schtasks.exe可以对计划任务进行管理&#xff0c;而不需要编写额外代码 这里掌握schtasks /CREATE 的几个核心参数就可以快速创建计划任务 /SC …

【短视频批量剪辑系统源代码开发部署技术分享】

多视频一键剪辑&#xff0c;创意无限升级 在确保音视频同步的基础之上&#xff0c;可视化剪辑技术再次迎来重大升级。最新引入的“多脚本升多视频”功能&#xff0c;标志着可视化剪辑矩阵的全面进步&#xff0c;为内容创作带来了前所未有的便利和灵活性。 这一功能的引入使得一…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十五章 Pinctrl和GPIO子系统实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

超声波眼镜清洗机有必要买吗?不踩坑的超声波眼镜清洗机选购攻略

超声波眼镜清洗机有必要买吗&#xff1f;当然有必要啦&#xff01;眼镜戴久&#xff0c;镜片难免会脏&#xff0c;镜片看起来会越来越模糊&#xff0c;不仅会影响清晰度还会影响美观&#xff0c;如果是经常戴妆出门的女生&#xff0c;镜托位置污垢也会越来越脏&#xff0c;有些…

windows下运行sh文件

1、打开git bash 2、进入sh文件所在文件夹&#xff0c;使用sh xx.sh运行

Python爬虫掌握-----4实战(爬取视频)

我们使用爬虫时难免会遇到爬取视频的情况&#xff0c;其实爬取图片视频&#xff0c;内容都是一样的。这里以b站视频为例。 一、开始 1.找到url&#xff0c;请求url 防盗链&#xff0c;需要写在UA伪装中 正常的三步&#xff1a; 1.url 2.requests请求 3.UA伪装 import req…

基于迁移学习的手势分类模型训练

1、基本原理介绍 这里介绍的单指模型迁移。一般我们训练模型时&#xff0c;往往会自定义一个模型类&#xff0c;这个类中定义了神经网络的结构&#xff0c;训练时将数据集输入&#xff0c;从0开始训练&#xff1b;而迁移学习中&#xff08;单指模型迁移策略&#xff09;&#x…

【性能优化】在大批量数据下使用 HTML+CSS实现走马灯,防止页面卡顿(二)

上一篇只是简单演示了’下一张’的操作和整体的设计思路,这两天把剩余功能补全了,代码经过精简,可封装当成轮播组件使用,详细如下. 代码 <template><div class"container"><button click"checkNext(last)">上一张</button><b…

C++之栈和队列使用及模拟实现

目录 栈的使用 队列的使用 栈的模拟实现 队列的模拟实现 deuqe容器介绍 在C语言中我们已经学习了栈和队列的相关性质&#xff0c;今天我们主要来学习C语法中栈和队列的相关概念。 栈的使用 在C中栈是一种容器适配器&#xff0c;在其内部适配了其它的容器&#xff0c;其相…

go程序在windows服务中优雅开启和关闭

本篇主要是讲述一个go程序&#xff0c;如何在windows服务中优雅开启和关闭&#xff0c;废话不多说&#xff0c;开搞&#xff01;&#xff01;&#xff01;   使用方式&#xff1a;go程序 net服务启动 Ⅰ 开篇不利 Windows go进程编译后&#xff0c;为一个.exe文件,直接执行即…

使用api 调试接口 ,配置 Header 、 body 远程调试 线上接口

学习目标&#xff1a; 目标 使用api 调试接口 &#xff0c;配置 Header 、 body 远程调试 线上接口 学习内容&#xff1a; 内容 设置请求方式 2. 选择 POST 提交 3.设置 Header 一般默认的 4个 header 属性就可以直接使用&#xff0c;如有特殊情况&#xff0c;需进行属性设…

Docusaurus VS VuePress:哪一个更适合你的技术文档?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

springcloud接入seata管理分布式事务

下载安装包 链接: seata 配置seata-server 文件上传Linux解压 压缩包我放在/usr/local/seata中 tar -zxvf seata-server-2.0.0.tar.gz修改配置文件 设置nacos为注册和配置中心 进入文件夹 cd /usr/local/seata/seata/conf修改application.yml文件 ...... ...... cons…

关键词查找【Aho-Corasick 算法】

【全程干货】程序员必备算法&#xff01;AC自动机算法敏感词匹配算法&#xff01;动画演示讲解&#xff0c;看完轻松掌握&#xff0c;面试官都被你唬住&#xff01;&#xff01;_哔哩哔哩_bilibili 著名的多模匹配算法 引入依赖&#xff1a; <dependency><groupId>…

ICMPv6与DHCPv6之网络工程师软考中级

ICMPv6概述 ICMPv6是IPv6的基础协议之一。 在IPv6报文头部中&#xff0c;Next Header字段值为58则对应为ICMPv6报文。 ICMPv6报文用于通告相关信息或错误。 ICMPv6报文被广泛应用于其它协议中&#xff0c;包括NDP、Path MTU发现机制等 ICMPv6控制着IPv6中的地址自动配置、地址…

前端知识--前端访问后端技术Ajax及框架Axios

一、异步数据请求技术----Ajax Ajax是前端访问后端的技术&#xff0c;为异步请求&#xff08;不刷新页面&#xff0c;请求数据&#xff0c;只更新局部数据&#xff09;。 例如&#xff1a;在京东网站中搜索电脑&#xff0c;就会出现一些联想搜索&#xff0c;但此时页面并没有…

AI行业合适做必应bing推广吗?怎么开户呢?

快速发展的AI行业中&#xff0c;有效的市场获客渠道是关键&#xff0c;随着数字营销领域的不断演进&#xff0c;必应Bing以其独特的市场定位、庞大的用户基础和高效的广告系统&#xff0c;成为AI企业推广策略中的重要一环。特别是针对那些寻求精准触达、高效转化的AI企业而言&a…