十五周算法训练营——二叉搜索树(BST)

news2024/11/24 20:09:23

今天是十五周算法训练营的第五周,主要讲二叉搜索树专题,包含:验证二叉搜索树、不同的二叉搜索树、二叉树的最近公共祖先、二叉搜索树的最近公共祖先。(欢迎加入十五周算法训练营,与小伙伴一起卷算法)

BST的特性:

  1. 对于BST的每一个节点node,左子树节点的值都比node的值要小,右子树节点的值都比node的值大;

  2. 对于BST的每一个节点node,它的左侧子树和右侧子树都是BST。

BST有一个重要的性质:BST的中序遍历结果是有序的(升序),也就是在中序位置可以将每个节点的值升序打印出来

void traverse(TreeNode root) {
     if (root == null) return;
     traverse(root.left);
     // 中序遍历代码位置
     print(root.val);
     traverse(root.right);
}

验证二叉搜索树

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

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

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

示例 1:

a76f2020790a7f04251cb319b9d2883e.jpeg
img

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

根据二叉搜索树特性,通过中序遍历获取。

// 解题思路:
// 1. 是否可以通过遍历一遍二叉树得到答案?
// 通过中序遍历可以得到,因为BST的中序遍历是一个升序结果
function isValidBST1(root) {
    let inorder = -Infinity;
    let result = true;
    let traverse = root => {
        if (root === null) {
            return;
        }

        // 前序位置
        traverse(root.left);
        // 中序位置
        // 中序位置获取当前值,并比较其和前一个值的大小,如果小于等于前一个值,则证明不是BST树,则将结果变为false
        if (root.val <= inorder) {
            result = false;
        } else {
            inorder = root.val;
        }
        traverse(root.right);
        // 后续位置
    };

    traverse(root);

    return result;
}

// 将中序遍历变为循环的方式
function isValidBST2(root) {
    if (root === null) {
        return true;
    }
    let head = root;
    const stack = [];
    let inorder = -Infinity;

    // 中序遍历的循环结构是需要创建一个栈,然后先将左节点全压入栈中
    while (stack.length > 0 || head !== null) {
        // 当head不为空时,压入栈中
        if (head !== null) {
            stack.push(head);
            head = head.left;
        } else {
            // 弹出栈顶元素
            head = stack.pop();
            if (inorder >= head.val) {
                return false;
            }
            inorder = head.val;
            head = head.right;
        }
    }

    return true;
}

// 2. 是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?
// 答案是可以的,因为搜索二叉树的性质是:
// (1)对于BST的每一个节点node,左子树节点值都比node的值要小,右子树节点的值逗比node的值大;
// (2)对于BST的每一个节点node,它的左侧子树和右侧子树都是BST
// 则我们解决该问题可定义一个递归函数来进行解决

// 注意:该问题需要借助外部变量,因为根节点的值必须大于左子树所有值、小于右子树所有值,所以需要引入最大最小值

function isValidBST3(root) {
    // 构造一个辅助函数,判断根节点是否在(min,max)范围内
    const helper = (root, min, max) => {
        if (root === null) {
            return true;
        }

        if (root.val <= min) {
            return false;
        }

        if (root.val >= max) {
            return false;
        }

        return helper(root.left, min, root.val) && helper(root.right, root.val, max);
    };

    // 初始状态min为-Infinity,max为Infinity
    return helper(root, -Infinity, Infinity);
}

不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 「二叉搜索树」 有多少种?返回满足题意的二叉搜索树的种数。

「示例 1:」

df20b154279cc9ecc9452d236cbbc6c3.jpeg
img
输入:n = 3
输出:5

利用的也是左子树值 < 根节点 < 右子树值

// 对于BST的每一个节点node,左子树节点的值都比node的值要小,右子树节点的值都比node的值大

// 在该问题中,1~n都有可能成为根节点,然后左边的是左子树上的点,右边的是右子树上的点,然后对应根节点的搜索树数量就是左子树的组合数 * 右子树的组合数;

// 该问题明显就转换为一个递归问题

function numTree(n) {
    // 为了解决子问题的重复问题,需要引入备忘录
    const memo = [];
    for (let i = 0; i < n + 1; i++) {
        memo.push([]);
        for (let j = 0; j < n + 1; j++) {
            memo[i].push(0);
        }
    }
    const count = (low, high) => {
        // 递归终止条件
        if (low > high) {
            return 1;
        }

        // 判断备忘录中是否存在该值,存在的话直接使用
        if (memo[low][high] > 0) {
            return memo[low][high];
        }
        let result = 0;

        for (let i = low; i <= high; i++) {
            const left = count(low, i - 1);
            const right = count(i + 1, high);

            result += left * right;
        }

        // 将结果存储到备忘录中
        memo[low][high] = result;

        return result;
    };

    return count(1, n);
}

console.log(numTree(3));

二叉树的最近公共祖先

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

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

示例 1:

4c2b42f4763705fcccff5b9b5dab4c6f.png
img

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

// 通过递归解决(后续遍历)
function lowestCommonAncestor(root, p, q) {
    // 确定递归函数
    const traverse = (node, p, q) => {
        // 确定递归终止条件
        if (node === null || node === p || node === q) {
            return node;
        }

        const left = traverse(node.left, p, q);
        const right = traverse(node.right, p, q);

        // 后续位置
        // 找到一个节点,其发现p、q节点分别出现在其左右子树上
        if (left !== null && right !== null) {
            return node;
        }

        // p或q本身就是最近公共祖先
        if (left === null) {
            return right;
        }

        return left;
    };

    return traverse(root, p, q);
}

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

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

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

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

示例 1:

f77e8848cd408bc4d6fc50874adffb01.png
img

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

// 因为是二叉搜索树,其是有顺序的,所以找最近公共祖先节点的问题就转换成了该节点在[p, q]中间即可
function lowestCommonAncestor(root, p, q) {
    const helper = (node, p, q) => {
        if (node === null) {
            return node;
        }

        if (node.val > p.val && node.val > q.val) {
            const left = helper(node.left, p, q);

            if (left !== null) {
                return left;
            }
        }

        if (node.val < p.val && node.val < q.val) {
            const right = helper(node.right, p, q);
            if (right !== null) {
                return right;
            }
        }

        return node;
    };

    return helper(root, p, q);
}

2448883a77ae74a4c90874ee9b270177.jpeg

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

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

相关文章

在SQL中使用AI【EVA】

EVA 旨在支持使用深度学习模型对结构化数据&#xff08;表格、特征向量&#xff09;和非结构化数据&#xff08;视频、播客、PDF 等&#xff09;进行操作的数据库应用程序。 它使用一系列受久经考验的关系数据库系统启发的优化&#xff0c;包括函数缓存、采样和基于成本的谓词重…

mybatisPlus初识

文章目录 什么是mybatisplus依赖入门案例自动填充乐观锁悲观锁乐观锁 mybatisPlus实现乐观锁批量查询根据指定条件查询 什么是mybatisplus mybatisplus是mybatis的增强工具&#xff0c;支持多种类型的数据库。 依赖 <dependency><groupId>com.baomidou</group…

我的服务器被挖矿了,原因竟是。。。

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 挖矿木马应急响应 一、什么是挖矿二、被挖矿主机现象三、挖矿木马处置思路1&#xff09;隔…

Git 使用教程:最详细、最正宗手把手教学(万字长文)

目录 一&#xff1a;Git二&#xff1a;SVN与Git的的区别三、安装Git四&#xff1a;常规操作五&#xff1a;远程仓库六&#xff1a;创建与合并分支七&#xff1a;bug分支八&#xff1a;多人协作九&#xff1a;git可视化工具 Git Git 是一种分布式版本控制系统&#xff0c;用于…

搭建免费的Plex媒体服务器 - 打造超级多媒体中心【异地远程连接】

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

科普, API 是这么演变而来的

API&#xff0c;全称为 Application Programming Interface&#xff0c;中文翻译为应用程序编程接口&#xff0c;是为了方便应用程序之间的数据和功能交互而设计的一些标准方法。API 的使用让开发者能够快速、高效地构建应用程序&#xff0c;从而加速了应用程序的开发速度。在 …

第二章物理层

1.物理层的基本概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流。物理层为数据链路层屏蔽了各种传输媒体的差异&#xff0c;使数据链路层只需要考虑如何完成本层的协议和服务&#xff0c;而不必考虑网络具体的传输媒体是什么。 物理层协议的主要任务 机…

21.ThreadLocal有哪些内存泄漏问题?如何避免?

ThreadLocal有以下几个内存泄漏问题&#xff1a; 长期不清理&#xff1a;如果ThreadLocal对象被长期占用&#xff0c;且不及时清理&#xff0c;会导致内存泄漏&#xff1b;使用static ThreadLocal&#xff1a;如果将ThreadLocal定义为static变量&#xff0c;就会导致它的生命周…

Java程序设计-案例:自由落体

程序模拟物体从10000米高空掉落后的反弹行为。 球体每落地一次&#xff0c;就会反弹至原高度的一半。按用户输入的弹跳次数&#xff0c;计算球体每次弹跳的高度。 实现过程&#xff1a; 1. 新建项目&#xff1b; 2. 接收 用户输入的弹跳次数&#xff1a; &#xff08;1&#…

全国快递物流 API 实现快递单号自动识别的原理解析

概述 全国快递物流 API 是一种提供快递物流单号查询的接口&#xff0c;涵盖了包括申通、顺丰、圆通、韵达、中通、汇通等600快递公司的数据。该 API 的目标是为快递公司、电商、物流平台等提供便捷、快速、准确的快递物流信息查询服务。 数据采集和处理 全国快递物流 API 的…

[深度学习]Ring All-reduce的数学性质

分布式深度学习里的通信严重依赖于规则的集群通信诸如 all-reduce, reduce-scatter, all-gather 等&#xff0c;因此&#xff0c;实现高度优化的集群通信&#xff0c;以及根据任务特点和通信拓扑选择合适的集群通信算法至关重要。 本文以数据并行经常使用的 all-reduce 为例来…

PCIe物理层链路训练和初始化(详细)总结附图文解析-PCIe专题知识(三)

目录 前言一、简介1.1 链路过程总结 二、基本概念2.1 常用字符序列2.1.1 TS1 TS2序列2.1.2 Idle序列2.1.3 FTS(Fast training sequence)序列2.1.4 SKIP序列 2.2 链路训练相关知识 三、具体过程3.1 detect状态3.2 polling状态3.3 config状态3.4 L0状态3.5 Recovery状态3.5.1 Rec…

【Git】全面详细了解开发者必备工具Git(2.0)

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 ajax学习 文章目录 学习目标起步——关于版本的控制  文件的版本  版本控制软件  使用版本控制软件的好…

Ubuntu 23.04 安装 Conda

Ubuntu 23.04 安装 Conda 1. 下载 Conda 安装脚本2. 运行安装脚本3. 安装完成后&#xff0c;关闭当前终端并打开新终端&#xff0c;这将激活 Conda4. 更新 Conda 至最新版本5. 添加必要的 Conda 通道以获取更多软件包6. 测试是否安装成功 1. 下载 Conda 安装脚本 wget https:/…

[Gitops--10]微服务项目部署流水线编写

微服务项目部署流水线编写 1. 部署环境说明 序号管理地址作用1192.168.31.199GitLab2192.168.31.104Harbor3192.168.31.131kubesphere 1.1 GitLab 1.2 流水线 1.2.1 创建流水线 1.2.2 创建凭证 1.2.3 创建kubeconfig凭证 这里需要注意的是,config中如果使用的是域名,那么需…

JavaWeb ( 三 ) Web Server 服务器

1.5.Web Server服务器 Web Server 服务器是一种安装在服务器主机上的应用程序, 用于处理客户端(Web浏览器)的请求&#xff0c;并返回响应内容。服务器使用HTTP(超文本传输协议)与客户机浏览器进行信息交流。 简单说就是将http协议的信息翻译成对应开发语言可以处理的对象信息。…

lombok常用的注解及使用方法

lombok是⼀种简化源码提⾼编程效率的⼯具&#xff0c;⽤于⽣成常⽤的代码。 如何使用lombok 引⼊依赖 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</ver…

【ChatGPT】吴恩达『提示工程』课程完全笔记下载

版权说明&#xff1a;『ChatGPT Prompt Engineering for Developers』是DeepLearning.AI出品的免费课程&#xff0c;版权属于DeepLearning.AI(https://www.deeplearning.ai/)。 本文是对该课程内容的翻译整理&#xff0c;只作为教育用途&#xff0c;不作为任何商业用途。 吴恩达…

Activiti7流程操作详解

一、Activiti流程操作步骤 定义流程&#xff0c;按照BPMN的规范&#xff0c;使用流程定义工具&#xff0c;用流程符号把整个流程描述出来 部署流程&#xff0c;把画好的流程定义文件&#xff0c;加载到数据库中&#xff0c;生成表的数据 启动流程&#xff0c;使用java代码来操…

4D成像雷达风口,谁在快速崛起?

4D成像雷达正进入规模量产落地的关键窗口期。 高工智能汽车注意到&#xff0c;毫米波雷达的发展某种程度上可以分为两个阶段&#xff1a;第一个阶段&#xff0c;传统毫米波雷达时代&#xff0c;市场基本被博世、大陆、安波福等国际Tier1巨头把持&#xff0c;市场格局长期稳固&…