LeetCode 98. 验证二叉搜索树

LeetCode 98. 验证二叉搜索树

1、题目

题目链接:98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:

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

示例 1:
image.png

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

示例 2:
image.png

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

提示:

  • 树中节点数目范围在[1, 104] 内
  • -231 <= Node.val <= 231 - 1

2、递归

思路

要解决这道题首先我们要了解二叉搜索树有什么性质可以给我们利用,由题目给出的信息我们可以知道:如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。
在中序遍历下,输出的二叉搜索树节点的数值是有序序列。
有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
可以递归中序遍历将二叉搜索树转变成一个数组,代码如下:

vector<int> vec;
void traversal(TreeNode* root) {
    if (root == nullptr) {
        return;
    }
    traversal(root->left);
    // 将二叉搜索树转换为有序数组
    vec.push_back(root->val);
    traversal(root->right);
}

然后只要比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素

traversal(root);
for (int i = 1; i < vec.size(); i++) {
    // 注意要小于等于,搜索树里不能有相同元素
    if (vec[i] <= vec[i - 1]) return false;
}
return true;

代码

class Solution {
public:
    vector<int> vec;
    void traversal(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        // 递归遍历左子树
        traversal(root->left);
        // 将二叉搜索树转换为有序数组
        vec.push_back(root->val);
        // 递归遍历右子树
        traversal(root->right);
    }

    bool isValidBST(TreeNode* root) {
        // 不加这句在leetcode上也可以过,但最好加上
        vec.clear();
        traversal(root);
        // 遍历数组,检查节点值是否递增
        for (int i = 1; i < vec.size(); i++) {
            // 注意要小于等于,搜索树里不能有相同元素
            if (vec[i] <= vec[i - 1]) {
                return false;
            }
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

3、递归(优化)

思路

以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。
这道题目比较容易陷入两个陷阱:

  • 陷阱1

不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了
写出了类似这样的代码:

if (root->val > root->left->val && root->val < root->right->val) {
    return true;
} else {
    return false;
}

我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。所以以上代码的判断逻辑是错误的。
例如: [10,5,15,null,null,6,20] 这个case:

节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!

  • 陷阱2

样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。
此时可以初始化比较元素为longlong的最小值。
问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?文中会解答。
了解这些陷阱之后我们来看一下代码应该怎么写:
递归三部曲:

  • 确定递归函数,返回值以及参数

要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。
注意递归函数要有bool类型的返回值,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。
其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。
代码如下:

long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root)
  • 确定终止条件

如果是空节点 是不是二叉搜索树呢?
是的,二叉搜索树也可以为空!
代码如下:

if (root == nullptr) return true;
  • 确定单层递归的逻辑

中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。
代码如下:

bool left = isValidBST(root->left);         // 左

// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val; // 中
else return false;

bool right = isValidBST(root->right);       // 右
return left && right;

整体代码如下:

class Solution {
public:
    long long maxVal = LONG_MIN;
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        // 递归判断左子树是否满足二叉搜索树的条件
        bool left = isValidBST(root->left);
        // 中序遍历,验证遍历的元素是不是从小到大
        // 如果当前节点的值小于maxVal,则更新maxVal为当前节点的值
        if (maxVal < root->val) {
            maxVal = root->val;
        } else {
            // 否则返回false,表示不满足二叉搜索树的条件
            return false;
        }
        // 递归判断右子树是否满足二叉搜索树的条件
        bool right = isValidBST(root->right);
        // 返回左子树和右子树都满足条件的结果
        return left && right;
    }
};

以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。
如果测试数据中有 longlong的最小值,怎么办?
不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。
代码如下:

代码

class Solution {
public:
    TreeNode* pre = nullptr;
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        // 递归判断左子树是否为有效二叉搜索树
        bool left = isValidBST(root->left);
        // 判断当前节点的值是否大于前一个节点的值(若前一个节点存在)
        if (pre != nullptr && pre->val >= root->val) {
            return false;
        }
        // 更新前一个节点为当前节点
        pre = root;
        // 递归判断右子树是否为有效二叉搜索树
        bool right = isValidBST(root->right);
        // 返回左子树和右子树都为有效二叉搜索树的结果
        return left && right;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

3、迭代

思路

代码

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> stk;
        TreeNode* cur = root;
        TreeNode* pre = nullptr;

        // 遍历二叉树
        while (!stk.empty() || cur != nullptr) {
            // 当前节点不为空,将其入栈,并将当前节点指向其左子节点
            if (cur != nullptr) {
                stk.push(cur);
                cur = cur->left;
            } else {
                // 当前节点为空,出栈,取出栈顶元素作为当前节点
                cur = stk.top();
                stk.pop();
                // 判断当前节点和前一个节点(非空)的值大小关系
                if (pre != nullptr && cur->val <= pre->val) {
                    return false;
                }
                // 更新前一个节点为当前节点
                pre = cur;
                // 将当前节点指向其右子节点
                cur = cur->right;
            }
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用apache和htaccess对目录访问设置密码保护配置教程

对目录设置密码保护配置说明 我们有时候访问某些网站的时候&#xff0c;要求输入用户名和密码才能访问。这是为了保护隐私&#xff0c;只让经过许可的人访问。 在本教程中主要介绍两种方法&#xff0c;一种是通过apache httpd.conf配置文件对管理后台目录设置密码保护&#xff…

LeetCode 700.二叉搜索树中的搜索

LeetCode 700.二叉搜索树中的搜索 1、题目 题目链接&#xff1a;700. 二叉搜索树中的搜索 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则…

Docker入门指南:Docker容器的使用(三)

&#x1f340; 前言 博客地址&#xff1a; CSDN&#xff1a;https://blog.csdn.net/powerbiubiu &#x1f44b; 简介 在本章节中&#xff0c;将深入探讨 Docker 容器的概念&#xff0c;以及容器的使用。 &#x1f4d6; 正文 1 什么是容器 1.1 Docker容器的介绍 Docker 容…

使用Gin编写Web API项目并自动化文档

最近需要使用Go写一个Web API项目&#xff0c;可以使用Beego与Gin来写此类项目&#xff0c;前文使用Beego创建API项目并自动化文档介绍了使用Beego来创建的Web API项目并自动化文档的方法。本文就介绍一下使用Gin来编写Web API项目并自动化文档。 一、创建项目 在创建Beego项…

栈与队列OJ题【括号适配问题】【用队列实现栈】【用栈实现队列】【设计循环队列】

一.有效的括号 ​​​OJ链接 这一道题我们就可以用栈来解决&#xff1a; 不了解栈的可以看我的上一篇博客。 typedef char STDataType; //用数组来实现栈 typedef struct stack {STDataType* a;int capacity;int top; }ST; void STInit(ST* pst) {assert(pst);pst->a NU…

基于SSM的理发店会员管理系统的设计和实现(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的理发店会员管理系统的设计和实现&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0…

泛微E9开发 添加多个多选框,实现单选框的效果

利用多个多选框实现单选框的效果 1、功能背景2、展示效果3、实现效果 1、功能背景 如下图所示&#xff0c;在表单中新增四个“选择框-复选框”类型的字段&#xff0c;并且设置其中的选项&#xff0c;每个多选框都只有一个选项&#xff0c;通过代码块实现单选框的效果 1.显示模…

ICode国际青少年编程竞赛- Python-5级训练场-综合练习7

ICode国际青少年编程竞赛- Python-5级训练场-综合练习7 1、 for i in range(6):while not Flyer[i].disappear():wait()Spaceship.step(2 2 * i)Spaceship.turnRight()2、 def get(a, b, c, d):for i in (a, b, c, d):Dev.step(i)if i ! 0:Dev.turnRight() get(3, 3, 5, -4)…

【CSP CCF记录】202206-2 寻宝!大冒险!

题目 过程 思路 1.绿化图坐标边界太大&#xff0c;不能直接用矩阵表示&#xff0c;可以用一个二维数组存储有树坐标的x,y值。 定义两个数组&#xff1a;绿化图arr[1005][2]、宝藏图数组b[55][55] 2. 依据条件&#xff0c;从绿化图中第一棵树的坐标开始区域遍历。统计绿化图…

spring cloud微服务example 入门第一个例子

新建Maven工程 删除src目录&#xff0c;修改poml.xml <modelVersion>4.0.0</modelVersion><groupId>org.example</groupId> <artifactId>SpringCloud_example</artifactId> <version>1.0-SNAPSHOT</version> <packaging&g…

物联网五层架构分析

物联网五层架构分析 随着科技的迅速发展&#xff0c;物联网&#xff08;IoT&#xff09;作为日常生活中不可或缺的一部分&#xff0c;已融入人们的生活和工作中。物联网五层架构&#xff0c;包括感知层、网络层、数据层、应用层和业务层&#xff0c;扮演着关键的角色。 感知层 …

WIFI模块的AT指令联网数据交互--第十天

1.1.蓝牙&#xff0c;ESP-01s&#xff0c;Zigbee, NB-Iot等通信模块都是基于AT指令的设计 初始配置和验证 ESP-01s出厂波特率正常是115200, 注意&#xff1a;AT指令&#xff0c;控制类都要加回车&#xff0c;数据传输时不加回车 1.2.上电后&#xff0c;通过串口输出一串系统…

【运维】如何安装ubuntu-24.04? 如何分区?

如何安装ubuntu-24.04&#xff1f;如何分区 经过一系列折腾&#xff0c;我总结了这几点&#xff1a; &#xff08;1&#xff09;在BIOS启动设置里&#xff0c;如果是GPT的硬盘格式&#xff0c;那么对应的就是UEFI的启动方式&#xff1b;如果是MBR的硬盘格式&#xff0c;那么对…

【Spring】GoF 之代理模式

一、代理模式 在 Java 程序中的代理模式的作用&#xff1a; 当一个对象需要受到保护的时候&#xff0c;可以考虑使用代理对象去完成某个行为 需要给某个对象的功能进行功能增强的时候&#xff0c;可以考虑找一个代理进行增强 A 对象无法和 B 对象直接交互时&#xff0c;也可以…

C# 使用Queue高效检索树行数据符合条件的数据,并返回完整树形数据示例

最近有项目需要加载大型树数据&#xff0c;数据大概3W条 后端使用C# NET6 前端使用Vue3 elementuiplus 虚拟tree 》解决大型树数据加载 遇到的问题是后端在检索数据时&#xff0c;要返回匹配数据的完整树目录 1.因为单条数据没有存放完整路径&#xff0c;需要通过父级ID逐…

【ARM Cortex-M 系列 2.1 -- Cortex-M7 Debug system registers】

请阅读【嵌入式开发学习必备专栏】 文章目录 Debug system registers中断控制状态寄存器&#xff08;ICSR&#xff09;Debug Halting Control and Status Register, DHCSR Debug 寄存器DCRSR与DCRDRCPU 寄存器读操作CPU 寄存器写操作CPU 寄存器选择CPU 寄存器读写示例 调试故障…

Ubuntu安装VScode

Ubuntu安装VScode 前言&#xff1a; 1、Ubuntu安装VScode比较方便 2、我更喜欢source insight 1、获取到linux版本的VScode安装包 VSCode 下载地址是&#xff1a;https://code.visualstudio.com/ 2、得到安装包 3、复制到ubuntu中&#xff0c;使用命令安装 sudo dpkg -i cod…

安卓短视频一键搬运软件_V1.5.2 高级版

短视频一键搬运app是一款非常实用的视频处理软件&#xff0c;拥有各种各样的视频处理功能&#xff0c;可以帮助用户进行视频的多项处理&#xff0c;首先用户可以在这里为视频去除水印&#xff0c;打开视频文件过后&#xff0c;再把视频里面的水印内容框选出来&#xff0c;这样就…

第三课,python基础语法(二),基本算术运算符、3种数据类型、变量命名规则

一&#xff0c;基本算术运算 数学中&#xff1a;&#xff0c;-&#xff0c;&#xff0c; *小练习 请在程序中&#xff0c;定义如下变量&#xff1a; 钱包余额(变量名&#xff1a;money)&#xff0c;初始余额50 请通过程序计算&#xff0c;再购买了&#xff1a; 冰淇淋10元可…

【C语言/数据结构】栈:从概念到两种存储结构的实现

目录 一、栈的概念 二、栈的两种实现方式 1.顺序表实现栈 2.链表实现栈 三、栈的顺序存储结构及其实现 1.栈的声明 2.栈的初始化 3.栈的销毁 4.栈的压栈 5.栈的弹栈 6.栈的判空 7.返回栈顶元素 8.返回栈的长度 四、栈的链式存储结构及其实现 1.栈的声明 2.栈的…