算法题解记录25+++验证二叉搜索树(百日筑基)

题目描述:

        难度:中等

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

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

  • 节点的左

    子树

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

示例 1:

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

示例 2:

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

提示:

  • 树中节点数目范围在[1, 10^4] 内
  • -2^31 <= Node.val <= 2^31 - 1

解题准备:

        1.题意:题目要求验证一棵二叉树,是不是二叉搜索树(BST,即Binary Search Tree),那么我们首先要知道,一棵二叉搜索树是什么样的。

        简单地说,BST有如下性质:第一,其左子树上所有节点的值,都小于根节点值;第二,其右子树上所有节点的值,都大于根节点的值。第三,BST下所有节点,都有BST的性质。

        2.基本操作:就题目看,不涉及增删改,验证必然要涉及查找遍历,姑且认为只有查找和比较。

        3.基础原理:面对树的题目,我们应敏锐地察觉到至少两种方法---BFS广度优先搜索和DFS深度优先搜索。这两种方法几乎是树的算法的基础,大多数算法,都是在二者之上优化而得。

解题思路:

        朴素地说,对于新手,其实一看到这个题目,是不会有什么思路的。

        然而,如果说做过一些题,就能直接得到答案,也非常夸张。

        我在此分享我的错误思路。

        错误思路---左右递归判断

                我最开始的思路比较简单,基于两个基本原理:

                第一,如果一棵树左子节点left的值,大于或等于根节点root的值,说明它不是BST。

                第二,如果一棵树右子节点right的值,小于或等于根节点root的值,说明不是BST。

                这两个原理没有错,却不完全,如果一棵树满足这两个原理,也有可能不是BST,比如下图,7大于5,却满足这个原理,同样,3小于5,也满足这个原理。

                这两个原理没有满足整体,所以我编写的代码在运行之后也报错了,不过,我会在下面提供代码。我先在此说明我的思考过程。【如何将这两个原理转化为代码的】

                第一步,有了原理,尽量往dfs或者bfs方法上靠近。

                第二步,发现判断一个节点是否满足2个原理,只需要判断left与root的关系、right与root的关系即可。【异常处理还没做】

                第三步,得到问题:虽然判断一个节点很简单,怎么判断下一个呢?

                第四步,初始的朴素思想:干脆中序遍历或前序遍历一遍,得到所有节点(存储List),依次进行判断?

                第五步,想到优化思路,既然中序遍历能够遍历每个节点,为什么要遍历一边,然后又从List再遍历一遍?

                第六步,中序原理是左根右,访问操作只在根节点做,那么继续左中右方法,把判断左子放在“左”,判断右子放在“右”,根节点的数据访问忽略了,就可以了。

                第七步,异常:访问到空节点null。

                第八步:if限制,null节点直接返回,又因为要判断子节点与根节点的关系,返回明显不能处理,所以需要额外判断子节点是否为null。

                完成。

        正确思路---中序遍历序列

                这个思路首先要明白一件事:中序遍历,其顺序是左子树、根节点、右子树。

                其中,在左子树left中,其顺序也是left的左子树、left、left的右子树。

                如果一棵树只有一个节点,毫无疑问它是升序的。

                假设这棵树root,它的左子树是left,右子树是right。

                那么,中序遍历时,一定会访问到left的最左子节点X,它是升序的。

                接着,访问最左子节点的父节点F,由于BST性质,最左子X小于F,所以是升序的。

                然后,访问F的右子【也有可能是右子的左子、右子的左子的左子……】P,由于BST性质,XFP升序排列。

                由于这棵树升序排列,那么,它的父树也升序排列,最后,left升序排列,整个节点的中序序列,都升序排列。

                由此,我们只需要得到中序序列,即可判断。

        正确思路---区间逼近

                这个思路是题解的思路,不过与我的错误思路非常接近,它把我的2个原理推进了。

                原理1:左子节点left,一定比root小,左子的左子,一定比root小;左子的右子,一定比root小,并且比左子大……

                原理2:右子节点right,一定比root大,右子的右子,一定比root大;右子的左子,一定比root大,并且比右子小。

                如此,我们可以发现,我的错误思路,就在于判断时,只是把节点与其左右子进行判断,忽略了最重要的根节点。

                然而,问题出现了:如果我们持续用root的值,作为判断依据,如下图:5作为右边所有节点的下限,没有问题,但是9却比7要大,也就是说子树7不符合BST的性质,所以整棵树不是BST。

                换句话说,我们要求上下限是不断变化的,这其实也是二叉树的精髓部分。

                怎么做呢?

                一般的想法是,维护两个变量low、up,使它们代表某个节点的上下限,在每次判断时作为依据。

                不过,如果这两个变量只是代表1个节点上下限,那么我们要开辟一个非常大的空间,用来存储所有的变量对low、up。

                所以,借助递归的方案,我们把维护变量对的操作交给系统,我们只需要递归调用时,传递变量对即可。

解题难点分析:

        无

错误代码---我的思路:

class Solution {public boolean isValidBST(TreeNode root) {boolean flag = true;// 为null不判断if(root.left!=null){if(root.left.val>=root.val){return false;}flag = isValidBST(root.left);}// 看一下是否不是BSTif(!flag){return flag;}// 同理if(root.right!=null){if(root.right.val<=root.val){return false;}flag = isValidBST(root.right);}return flag;}
}

代码---中序遍历:

class Solution {public boolean isValidBST(TreeNode root) {// 存储节点List<Integer> data = new ArrayList<>();fuzhu(root, data);// 依次判断for(int i=0; i<data.size()-1; i++){if(data.get(i) >= data.get(i+1)){return false;}}return true;}private void fuzhu(TreeNode root, List<Integer> data){if(root==null){return;}fuzhu(root.left, data);data.add(root.val);fuzhu(root.right, data);}
}

代码---迭代上下限:

class Solution {public boolean isValidBST(TreeNode root) {return fuzhu(root, Long.MIN_VALUE, Long.MAX_VALUE);}private boolean fuzhu(TreeNode root, long low, long up){// 如果是空节点,即正确 if(root==null){return true;}// 如果超出界限,则错误// 我们可以看出来,每次递归判断,只能判断一个节点,我们不可能一起判断左子和右子if(root.val <= low || root.val >= up){return false;}// 返回左子节点、右子节点的判断。return fuzhu(root.left, low, root.val) && fuzhu(root.right, root.val, up);}
}

以上内容即我想分享的关于力扣热题25的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

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

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

相关文章

软件开发模型介绍

软件开发模型&#xff08;Software Development Model&#xff09;是指软件开发全部过程、活动和任务的结构框架。它清晰、直观地表达软件开发全过程&#xff0c;明确规定了要完成的主要活动和任务&#xff0c;用来作为软件项目工作的基础。 一、常见的软件开发模型&#xff1…

【MISRA-C-2012】:标准的理解与学习

标准的理解与学习 引用二、Misra-C 规则Misra-C全解读 - Rule 1 标准的C语言环境&#xff08;待更新&#xff09;Misra-C全解读 - Rule 2 未使用的代码&#xff08;待更新&#xff09;Misra-C全解读 - Rule 3 注释&#xff08;待更新&#xff09;Misra-C全解读 - Rule 4 字符与…

ThinkPHP+MySQL查询数据的时候计算两个经纬度之间的距离并根据距离进行筛选

原需求实现说明 新增了一个按距离进行筛选的需求。需要把查询代码做如下修改 /*** 求职意向* return void* throws \think\exception\DbException*/public function get_lists(){$request $this->request->get();if(empty($request[lng]) || empty($request[lat])){$th…

如何抠图?6个简单方便的抠图软件教你自己快速抠图

如何抠图&#xff1f;6个简单方便的抠图软件教你自己快速抠图 抠图是图像处理中常见的操作之一&#xff0c;它可以帮助我们从一幅图像中抠出特定的部分&#xff0c;通常用于制作合成图、更换背景或修改图像内容。下面介绍的6款简单方便的抠图软件可以帮助您快速进行抠图操作&a…

PyQt:界面无边框+实现窗口最小化(任务栏图标隐藏+托盘图标显示)

一、整体实现效果 诸如WX、各种管家的桌面显示方式。窗口关闭后&#xff0c;往往是任务栏图标消失&#xff0c;保持右下角托盘图标显示&#xff0c;保持后台运行。双击托盘图标后&#xff0c;窗口显示。 二、代码实现 from PyQt5.QtWidgets import * from ato_upgrade impo…

失效模式分析的适用范围与注意事项——SunFMEA软件

失效模式分析对产品从设计完成之后&#xff0c;到首次样品的发展而后生产制造&#xff0c;到品管验收等阶段都可说皆有许多适用范围&#xff0c;基本上可以活用在3个阶段。 一、设计阶段的失效模式分析 1.针对已设计的构想作为基础&#xff0c;逐项检讨系统的构造、机能上的问…

CSS常用滤镜效果

CSS 提供了多种滤镜效果&#xff0c;可以通过 filter 属性应用于 HTML 元素。以下是一些常用的 CSS 滤镜效果&#xff1a; 一、灰度 (Grayscale) 将图像转换为灰度图像。值在 0%&#xff08;原始图像&#xff09;和 100%&#xff08;完全灰度&#xff09;之间。 filter: gra…

qt信号和槽之间传送其他数据类型

提交信号和接受槽文件里分别全局声明该结构 Q_DECLARE_METATYPE (can) 在提交信号的时候将该数据结构set到QVariant里 在槽的接收里 &#xff0c;直接.value强转为声明的自定义结构里 void MainWindow::canrecvdeal(QVariant sy)//CAN_FRAME_MAG v { CAN_FRAME_MAG v; vsy.valu…

android进阶-回调

回调&#xff08;Callback&#xff09;是一种常见的编程模式&#xff0c;用于处理异步事件或信息传递。通过回调&#xff0c;一个对象&#xff08;通常是一个事件的发起者或处理者&#xff09;可以将某些任务或行为的执行通知给另一个对象 常见例子&#xff1a; 事件监听器&a…

Next.js+TS项目中的错误边界处理与渲染降级实践

在开发基于Next.js的TypeScript应用程序时&#xff0c;我们经常会遇到一些意料之外的JavaScript错误&#xff0c;这些错误可能会导致页面直接白屏&#xff0c;严重影响用户体验。为了提升应用的健壮性和用户体验&#xff0c;引入ErrorBoundary组件是一种非常有效的策略。本文将…

【回溯 栈 代数系统 动态规划】282. 给表达式添加运算符

本文涉及知识点 回溯 栈 代数系统 动态规划 LeetCode 282. 给表达式添加运算符 给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target &#xff0c;在 num 的数字之间添加 二元 运算符&#xff08;不是一元&#xff09;、- 或 * &#xff0c;返回 所有 能够得到 ta…

Rust 中的声明可见性

Rust 中的声明可见性 在 Rust 编程语言中&#xff0c;声明可见性是一个核心概念&#xff0c;它决定了代码中的项&#xff08;如函数、结构体、枚举等&#xff09;在哪些范围内可以被访问。Rust 通过一套严谨的规则来控制这些可见性&#xff0c;以确保代码的安全性和封装性。下…

Ngnix VTS模块添加和测试

目录 VTS模块介绍 上传软件包xftp/lrzsz 执行脚本 添加vts的配置 测试 测试&#xff1a;nginx.conf配置文件是否有语法错误 测试&#xff1a;windows机器上访问效果 VTS模块介绍 Nginx VTS模块&#xff08;nginx Virtual Host Traffic Status Module&#xff09;是一个第三…

【C++初阶】string模拟实现

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

【精读Yamamoto】方向性连接如何丰富神经网络的功能复杂度 | 体外神经元培养实验 | 脉冲神经元模型(SNN) | 状态转移模型

探索大脑的微观世界&#xff1a;方向性连接如何丰富神经网络的功能复杂度 在神经科学领域&#xff0c;理解大脑如何通过其复杂的网络结构实现高级功能一直是一个核心议题。最近&#xff0c;一项由Nobuaki Monma和Hideaki Yamamoto博士领导的研究为我们提供了新的视角&#xff…

cuttag学习笔记

由于课题可能用上cut&tag这个技术&#xff0c;遂跟教程学习一波&#xff0c;记录一下以便后续的学习&#xff08;主要是怕忘了&#xff09; 教程网址cut&tag教程 背景知识&#xff1a;靶标下裂解与标记&#xff08;Cleavage Under Targets & Tagmentation&#xf…

什么是跨境物流管理系统,它有什么功能

对于从事跨境物流的物流商来说&#xff0c;提升物流效率是一直都需要考虑的问题。不过不用担心&#xff0c;跨境物流系统&#xff08;TMS&#xff09;是个不错的解决方案。 谁应该使用跨境物流管理系统&#xff1f; 可以说&#xff0c;跨境物流系统最大的特点就是使用上的灵活性…

90后医生下班摆摊就能赚1500?看内行人是如何分析的?2024普通人逆袭的机会,2024普通人想翻身的风口行业

“在自己空余的时间&#xff0c;做点自己喜欢的事情”这就是浙江义乌的王医生&#xff0c;摆摊被采访时的回答。王大夫说&#xff0c;自己兼职已经有半年多了&#xff0c;每天的营业额能达到1500元。同时王医生表示&#xff0c;自己的目标是开一间自己的小店。 看到这里&#x…

新版Idea配置仓库教程

这里模拟的是自己搭建的本地仓库环境&#xff0c;基于虚拟机搭建利用gogs创建的仓库 1、Git环境 你需要准备好git和仓库可以使用github 、gitee等 1.1 拉取代码 本项目使用 Git 进行版本控制&#xff0c;在 gogs 上创建一个个人使用的 git 仓库&#xff1a; http://192.168.…

1.5.2 基于XML配置方式使用Spring MVC

用户登录演示效果 实战概述&#xff0c;可以帮助你更好地理解整个流程。 项目创建 创建了一个名为 SpringMvcDemo01 的 Jakarta EE 项目。通过 Maven 添加了项目所需的依赖&#xff0c;包括 Spring MVC、JSTL 等。 视图层页面 创建了登录页面&#xff08;login.jsp&#xff0…