leetcode98. 验证二叉搜索树关于递归实现中遇到的global和nonlocal(各种报错分析)

news2025/3/11 6:40:00

leetcode98. 验证二叉搜索树

题目

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

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

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

在这里插入图片描述
输入:root = [2,1,3]
输出:true

在这里插入图片描述
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

思路和代码:

首先这题的解决办法可以分为两种,递归和迭代。

怎么判断一棵树是不是二叉搜索树?二叉搜索树的中序遍历是有序的。所以中序遍历,依次处理节点发现其严格递增,则必然是二叉搜索树。

一.递归

1.递归序

简单回顾一下二叉树的前中后序遍历的递归实现。

在这里插入图片描述

对于这样的一棵二叉树,它的递归序是什么?

def f(root):
    # 空节点就返回
    if not root:
        return root
    # 1.以上两行代码是第一次来到自己
    # 左边递归遍历
    f(root.left)
    # 2.第二次来到自己 左树遍历完了之后得回到自己 然后才能调用下一句
    # 右边递归遍历
    f(root.right)
    # 3.第三次来到自己 右树遍历完了之后第三次回到自己 只有回到自己才知道下行没语句了 自己才能返回
   

以上面的二叉树为例,先得到1,2,4,发现4的左边点为空,则第二次回到4,4的右节点为空,第三次回到4,此时序列是1,2,4,4,4。

2的左子树遍历完了之后又回到2,遍历2的右节点5, 5的做节点为空,第二次到5, 5的右节点为空,第三次到5…以此类推,得到最终的递归序是——

1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1。

对于每一个节点都会有三次遍历。

2.二叉树的前中后序递归实现

前(先)序遍历是第一次遍历到该节点的时候处理节点(打印或者加到结果集中);中序是第二次遍历到该节点的时候处理节点;后续是第三次遍历到该节点的时候处理节点。

递归实现前序遍历(leetcode144):

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        def preOrder(root):
            if not root:
                return []
            res.append(root.val)
            preOrder(root.left)
            preOrder(root.right)
        res = []
        preOrder(root)
        return res

递归实现中序遍历的代码(leetcode94):

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        def inOrder(root):
            if not root:
                return res
            inOrder(root.left)
            res.append(root.val)
            inOrder(root.right)
        res = []
        inOrder(root)
        return res

递归实现后序遍历(leetcode145):

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        def postOrder(root):
            if not root:
                return res
            postOrder(root.left)
            postOrder(root.right)
            res.append(root.val)
        res = []
        postOrder(root)
        return res

3.本题的递归代码实现

回到这题中,在递归实现中序遍历的基础上改代码。第二次遍历到节点的时候不是再把节点值加到结果列表中,而是判断是否升序,不是返回False,是就更新当前节点值。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        prevalue = float("-inf")
        def isBST(root):
            # 空节点返回True
            nonlocal prevalue
            if not root:
                return True
            # 递归左子树 左子树不是二叉搜索树 直接返回False
            if not isBST(root.left):
                return False
            else:
                # 当前遍历的值应该比prevalue大 是则更新prevalue 否则返回False
                if root.val <= prevalue:
                    return False    
                else:
                    prevalue = root.val
            # 递归右子树
            return isBST(root.right)
        return isBST(root)

还有一个容易想到的简单粗暴的方法就是得到中序后进行去重排序,看是否和之前的中序结果是否一样,一样则说明是二叉搜索树。

代码:

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def inOrder(root):
            if not root:
                return []
            inOrder(root.left)
            res.append(root.val)
            inOrder(root.right)
        res = []
        inOrder(root)
        inorder = res
        return res == sorted(res) and len(res) == len(set(res))

4.nonlocal

上面的代码里,在prevalue前申明了nonlocal,非局部变量。不申明的话就会报错:

UnboundLocalError: local variable ‘prevalue’ referenced before assignment

在“if root.val <= prevalue:”这句代码报错,说局部变量prevalue在被赋值之前就被引用了。nonlocal声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量。
nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误。

最上层的函数使用nonlocal修饰变量必定会报错“SyntaxError: no binding for nonlocal ‘prevalue’ found”(没有找到本地绑定的prevalue,就是说上一级函数里没有这个变量),如下:

在这里插入图片描述

5.global

我发现用关键词global也能通过,但是需要在函数外面里面共申明两次,代码如下:

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        global prevalue
        prevalue = float("-inf")
        def isBST(root):
            global prevalue
            # nonlocal prevalue
            # 空节点返回True
            if not root:
                return True
            # 递归左子树 左子树不是二叉搜索树 直接返回False
            if not isBST(root.left):
                return False
            else:
                # 当前遍历的值应该比prevalue大 是则更新prevalue 否则返回False
                if root.val <= prevalue:
                    return False    
                else:
                    prevalue = root.val
            # 递归右子树
            return isBST(root.right)
        return isBST(root)

如果isBST外面的“global prevalue”注释掉,会报错“NameError: name ‘prevalue’ is not defined”,如果外面有,函数里面没有申明global prevalue,又会报错“UnboundLocalError: local variable ‘prevalue’ referenced before assignment”。

就对global有点好奇。

global关键字用来在函数或其他局部作用域中使用全局变量。

函数内部可以改变函数外部的变量。如:

a = 1
def test():
    global a
    a += 1
test()
print(a)

此段代码输出值是2。

问,为什么这题中prevalue要申明两遍才能通过?

我的理解是,在改变a值的时候a就是全局变量,但是在def isValidBST()函数下的变量是这个函数内的内部变量(局部变量),需要先申明它是全局变量,然后def isBST()函数是在这个函数里面又嵌套的一个函数,在这个函数里面改变prevalue的值需要在申明prevalue是全局变量。

感觉确实是这个样子,如下面的测试代码。在test1函数里面申明的变量a不是全局变量,是局部变量!如果不申明一下,在test2()里面就算申明了global a依然会报错。但如果a写在test1外面,即使在test1里面把global a注释掉不再报错。

在这里插入图片描述

但是,在这题里,写成下面的代码会报错,与之前的区别在于把prevalue定义在isValidBST()外面,而isValidBST()里面没有再嵌套一个函数。

如果在prevalue定义前也审明一下global不会报错,但是会有错误的案例通不过,如[0]。我在isValidBST()输出了prevalue的值,发现在系统测试不同样例的时候,在报错的样例中prevalue会保存为上一个样例的值而不是-inf。反正就,蛮奇怪的。

在这里插入图片描述

二.迭代

1.二叉树的前中后序迭代实现

迭代不像递归有统一的逻辑,前中后序的逻辑各不相同。

前序遍历(头左右):

先把根节点放入栈中,然后弹出一个节点,处理该节点(打印或加入到结果集中),把该节点的右左孩子节点压入栈(如果有),注意顺序是右左,周而复始。

代码:

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        stack = [root]
        while stack:
            node = stack.pop()
            res.append(node.val)
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return res

后序遍历(左右头):

后序和先序比较相似,所以先说后序遍历。

还是一样,先把根节点加入栈中,弹出一个节点,把这个节点放到另一个辅助栈中,然后再把弹出节点的左右孩子节点加入到栈中,注意是左右顺序。这样入栈的顺序是头左右,到辅助栈的入栈顺序就是头右左,周而复始,最后弹出辅助栈的结果就是后序遍历,左右头。

代码:

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        stack = [root]
        while stack:
            node = stack.pop()
            res.append(node.val)
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right) 
        return res[::-1]

中序遍历(左头右)

和前后序遍历的逻辑不一样,中序遍历是先把左右的左边界的节点入栈,然后弹出一个节点,进行处理(打印或者加入到结果集中),对弹出节点的右节点也这么干,周而复始。

代码:

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        stack = []
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                root = stack.pop()
                res.append(root.val)
                root = root.right
        return res

注意循环条件不再是while stack而是while stack or root。

2.本题的迭代实现代码

先中序遍历在排序和比较去重前后长短的代码就不贴了,就是中序遍历的代码后面再加一句判断。

迭代实现本题递归实现本题的思路本质都是一样的,在本来将节点值加入结果集处进行一下比较即可。

代码:

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        prevalue = float("-inf")
        stack = []
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                root = stack.pop()
                # 在这块处理
                if root.val <= prevalue:
                    return False
                else:
                    prevalue = root.val
                root = root.right
        return True

综上,这题目直接中序遍历,在此基础上稍稍改动就完了。

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

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

相关文章

前端基础(十三)_定位position、定位层级z-index

一、定位position Css的定位机制&#xff1a;普通文档流、浮动、定位 这里主要介绍CSS的定位属性&#xff1a;position&#xff1a; 1、定位原理&#xff1a;允许元素相对于正常位置、或者相对于父元素、浏览器窗口本上的位置 2、元素位置的调整&#xff1a; left|right属性、…

Spring Boot项目使用RabbitMQ队列

Spring Boot项目使用RabbitMQ队列 一、Rabbitmq的安装 RabbitMQ是一个开源的遵循 AMQP协议实现的基于 Erlang语言编写&#xff0c;**即需要先安装部署Erlang环境再安装RabbitMQ环境。 erlang的安装在windows中直接点击安装即可。 安装完erlang后设置erlang的环境变量ERLANG…

CSM32RV20 是 32位低功耗MCU芯片 RISC-V RV32IMAC 内核

CSM32RV20 是 32位低功耗MCU芯片 RISC-V RV32IMAC 内核 CSM32RV20 是基于RISC-V RV32IMAC 内核&#xff08;2.6 CoreMark/MHz&#xff09;的32位低功耗MCU芯片&#xff0c;最高主频32MHz&#xff0c;最大支持 40KB 嵌入式FlASH、4KB SRAM和512B NVM&#xff0c;集成ADC和UART、…

如何使用FastReport .NET 从 JetBrains Rider 中创建PDF报告?

Fastreport是目前世界上主流的图表控件&#xff0c;具有超高性价比&#xff0c;以更具成本优势的价格&#xff0c;便能提供功能齐全的报表解决方案&#xff0c;连续三年蝉联全球文档创建组件和库的“ Top 50 Publishers”奖。 FastReport.NET官方版下载&#xff08;qun&#x…

了解什么是架构基本概念和架构本质

什么是架构和架构本质 在软件行业&#xff0c;对于什么是架构&#xff0c;都有很多的争论&#xff0c;每个人都有自己的理解。此君说的架构和彼君理解的架构未必是一回事。因此我们在讨论架构之前&#xff0c;我们先讨论架构的概念定义&#xff0c;概念是人认识这个世界的基础&…

Note that you can also install from a tarball 处理

近期使用 npm publish 发布依赖包时&#xff0c;始终遇到 npm 404 报错&#xff0c;错误信息是 “Note that you can also install from a tarball”&#xff0c;尝试更换网络&#xff0c;更换代理服务器等操作&#xff0c;都无效&#xff0c;npm 报错如下 问题原因&#xff1a…

python通过字典来替代if..else

在应对多策略的场景下&#xff0c;大量使用if...else...不仅提高了后期的维护成本&#xff0c;还降低了运行效率。通过字典做映射就可以更好的优化代码。 比如这样一个模拟场景&#xff0c;根据用户的VIP等级&#xff0c;发放奖励。在大量使用if...else...时就会变成如下状态&…

nodejs篇 内置模块http 常用api

文章目录前提创建一个最基本的http服务器req有哪些值得关注的信息res常用的apihttp.request(options[, callback])server事件监听checkContinuecheckExpectationcloseconnect前提 如果你觉得nodejs官方文档给的api太多&#xff0c;不知道哪些重要&#xff0c;请看下去&#xf…

TensorRT全方位概览笔记

TensorRT &#xff08;基于8.2.3&#xff09;1.简介1.export1.1 使用tensorrt API 搭建1.2 使用parser1.3 使用框架内 tensorrt 接口1.4 注意事项2.开发辅助工具2.1 trtexec2.2 Netron2.3 onnx-graphsurgeon2.4 polygraphy2.5 Nsight Systems3. plugin3.1 plugin3.2 使用3.3 类…

就离谱!使用机器学习预测2022世界杯:小组赛挺准,但冠亚季军都错了 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 数据分析实战系列&#xff1a;https://www.showmeai.tech/tutorials/40 &#x1f4d8; 机器学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/41 &#x1f4d8; 本文地址&#xff1a;https://www.sho…

C2. Potions (Hard Version)(可以后悔的选取 + 一种新奇的优先队列用法)

Problem - 1526C2 - Codeforces 这是该问题的困难版本。唯一不同的是&#xff0c;在这个版本中&#xff0c;n≤200000。只有当两个版本的问题都解决了&#xff0c;你才能进行黑客攻击。 有n个药水排成一行&#xff0c;最左边是药水1&#xff0c;最右边是药水n。每种药水在喝下…

Eclipse安装和环境的基本配置

Eclipse安装 安装包 链接&#xff1a;https://pan.baidu.com/s/13LXiyGmgdAQ2MYXhim1WMg 提取码&#xff1a;WADS 不会安装的可以参考这篇文章 链接: 安装教程文章 eclipse怎么更改存储位置 1.1 file-> Switch Workspace ->Other 打开后可以看到保存文件的路径也可以…

疫情之下连锁餐饮行业积极求变,集团采购协同管理系统重构企业采购数字化

2019年底至今&#xff0c;新冠肺炎疫情已进入了第三个年头。作为接触性、聚集性行业&#xff0c;国内餐饮业持续承压&#xff0c;经历了一系列的波折。尤其2022年以来&#xff0c;国内多地出现了此起彼伏的疫情&#xff0c;给餐企带来了较大冲击&#xff0c;餐饮行业整体营收收…

【推荐】安全访问服务边缘(SASE)资料汇总合集24篇

Secure Access Service Edge (SASE) 是Gartner推出的一个新的技术理念。SASE将 SD-WAN和网络安全解决方案&#xff08;FWaaS、CASB、SWG 和ZTNA&#xff09;融合到统一的云原生服务中。SASE是Gartner最新提出的一个技术理念&#xff0c;SASE用于从分布式云服务交付聚合的企业网…

Web前端105天-day49-jQuery

jQuery02 目录 前言 一、复习 二、标签内容 三、get请求 四、新增子元素 五、委托 六、克隆 七、加载HTML 八、准备就绪 九、Node.js 十、js中提示jQuery的方案 十一、location 十二、根据地址栏参数加载页面 十三、全局样式变量 总结 前言 jQuery02学习开始 一…

vi\vim编辑器的使用及命令、快捷键

vi\vim编辑器 1、vi\vim编辑器介绍 vi\vim是visual interface的简称&#xff0c;是Linux中最经典的文本编辑器。 同图形化界面中的文本编辑器一样&#xff0c;vi是命令行下对文本文件进行编辑的绝佳选择。 vim是vi的加强版本&#xff0c;兼容vi的所有指令&#xff0c;不仅能…

信息化时代,相比于人工服务客户更喜欢自助式服务

对于SaaS产品&#xff0c;为客户提供自助式服务&#xff0c;帮助客户能够自行完成任务和解决问题&#xff0c;给到客户更好的使用体验&#xff0c;对于SaaS产品&#xff0c;搭建一个自助式知识库门户和产品知识库尤为重要。在选购产品后&#xff0c;如果没有获得很好的客户服务…

TraceView使用

TraceView 是什么&#xff1f; TraceView 是 Android SDK 中内置的一个工具&#xff0c;用于加载 trace 文件&#xff0c;用图形的形式展示代码的执行时间、调用次数及调用栈&#xff0c;便于我们分析。 如何生成 trace 文件&#xff1f; 使用代码生成 Debug.startMethodTra…

新零售时代下,物流行业迎来新机遇

电商&#xff0c;一个异军突起的新行业&#xff0c;将改变传统商业旧模式。根据相关数据显示&#xff0c;2010-2019年&#xff0c;我国电商交易规模从4.5万亿元增长到34.8万亿元&#xff0c;年均复合增速达25%。在电商飞速发展的态势下&#xff0c;物流行业也迎来了蓬勃发展。 …

【华为OD机试真题2023 JAVA】去除多余空格

华为OD机试真题,2023年度机试题库全覆盖,刷题指南点这里 去除多余空格 知识点字符串数组队列 时间限制:2s 空间限制:256MB 限定语言:不限 题目描述: 去除文本多余空格,但不去除配对单引号之间的多余空格。给出关键词的起始和结束下标,去除多余空格后刷新关键词的起始和…