2-树-恢复二叉搜索树

news2024/11/27 6:26:29

这是树的第二篇算法,力扣链接。

给你二叉搜索树的根节点 root ,该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下,恢复这棵树 

示例 1:

输入:root = [1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3 不能是 1 的左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。

示例 2:

输入:root = [3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。

上次算法提到了中序遍历:

中序遍历 (Inorder Traversal)

在中序遍历中,我们按照以下顺序遍历树中的节点:

  1. 遍历左子树
  2. 访问根节点
  3. 遍历右子树

对于二叉搜索树(BST),中序遍历会按照升序访问所有节点,因为二叉搜索树的特点是左子节点的值小于根节点的值,根节点的值小于右子节点的值。

中序遍历会将树一维化并且是有序的:

假设有一棵二叉树如下:

    A
   / \
  B   C
 / \   \
D   E   F

对这棵树进行不同的遍历会得到以下结果:

  • 中序遍历D, B, E, A, C, F。首先遍历左子树(D, B, E),然后是根节点(A),最后是右子树(C, F)。 

因此这里的第一个解法是将树一维化,然后找到非有序的节点,交换两个节点位置即可。

func recoverTree(root *TreeNode) {
	nodeList := make([]*TreeNode, 0)
	stack := make([]*TreeNode, 0)
	cur := root
	for len(stack) > 0 || cur != nil {
		for cur != nil {
			stack = append(stack, cur)
			cur = cur.Left
		}
		cur = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		nodeList = append(nodeList, cur)
		cur = cur.Right
	}
	index1, index2 := -1, -1
	for i := 0; i < len(nodeList)-1; i++ {
		if nodeList[i+1].Val < nodeList[i].Val {
			index2 = i + 1
			if index1 == -1 {
				index1 = i
			} else {
				break
			}
		}
	}
	nodeList[index1].Val, nodeList[index2].Val = nodeList[index2].Val, nodeList[index1].Val
}

这道题的进阶方法是不用额外的空间复杂度。

先不讲这道题怎么做,先试一下不用额外空间复杂度的中序遍历。这个做法叫做Morris 中序遍历,其核心思路是利用叶子节点右侧的空指针指向中序遍历的后继节点,从而避免了栈的使用。

操作思路是首先检查当前节点的左子节点。如果左子节点不存在,我们输出当前节点并将其右子节点作为下一个当前节点。如果左子节点存在,我们找到当前节点在其左子树上的前驱节点,这是其左子树中最右侧的节点。如果前驱节点的右子节点为空,我们将其设置为当前节点,并将当前节点更新为其左子节点。如果前驱节点的右子节点是当前节点,我们将其重新设置为空,输出当前节点,并将当前节点更新为其右子节点。

这种方式的遍历确保了每个节点都被访问两次,但只有在第二次访问时才会输出。在整个过程中,我们没有使用栈或递归,从而实现了常数空间复杂度。

这个做法很神奇,不求一次性就能记住,多少留个印皙那个每次都可以回看:

func morrisTraversal(root *TreeNode) {
	nodeList := make([]*TreeNode, 0)
	cur := root
	for cur != nil {
		if cur.Left == nil {
			nodeList = append(nodeList, cur)
			cur = cur.Right
		} else {
			pre := cur.Left
			for pre.Right != nil && pre.Right != cur {
				pre = pre.Right
			}
			if pre.Right == nil {
				pre.Right = cur
				cur = cur.Left
			} else {
				pre.Right = nil
				nodeList = append(nodeList, cur)
				cur = cur.Right
			}
		}
	}
}

至于这道题怎么利用morris解,最简单的当然还是用数组找索引,但是为了不用额外空间,我们可以考虑原地更改,不过需要记录索引。

func recoverTree(root *TreeNode) {
	var first, second, prev *TreeNode
	cur := root
	for cur != nil {
		if cur.Left == nil {
			if prev != nil && cur.Val < prev.Val {
				if first == nil {
					first = prev
				}
				second = cur
			}
			prev = cur
			cur = cur.Right
		} else {
			pre := cur.Left
			for pre.Right != nil && pre.Right != cur {
				pre = pre.Right
			}
			if pre.Right == nil {
				pre.Right = cur
				cur = cur.Left
			} else {
				pre.Right = nil
				if prev != nil && cur.Val < prev.Val {
					if first == nil {
						first = prev
					}
					second = cur
				}
				prev = cur
				cur = cur.Right
			}
		}
	}
	first.Val, second.Val = second.Val, first.Val
}

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

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

相关文章

Google Gemini Pro 国内版

Google Gemini Pro 国内版&#xff1a;【直达链接】 Google Gemini Pro 国内版 能力分类基准测试描述更高分数更好Gemini UltraGPT-4通用MMLU57个主题&#xff08;包括STEM、人文等&#xff09;的问题表示是90.0%86.4%&#xff08;5-shot, 报告&#xff09;推理Big-Bench Hard…

linux系统上C程序的编译、运行及调试-gcc

gcc -o timer timer.c &#xff1a;生成可执行文件main&#xff0c;依托main.c,也可依托多个文件./timer :运行代码

C# 根据USB设备VID和PID 获取设备总线已报告设备描述

总线已报告设备描述 DEVPKEY_Device_BusReportedDeviceDesc 模式 winform 语言 c# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Window…

【软件设计师笔记】计算机系统基础知识考点

&#x1f413; 计算机系统组成 计算机系统是由硬件和软件组成的&#xff0c;它们协同工作来运行程序。计算机的基本硬件系统由 运算器、控制器、存储器、输入设备和输出设备5大部件组成。运算器、控制器等部件被集成 在一起统称为中央处理单元&#xff08;Central Processing …

幻兽帕鲁服务器怎么更新?教你一行命令搞定!

今天发现幻兽帕鲁又进行了一次更新&#xff0c;所以我们的服务器也是需要同步更新的 那么&#xff0c;我们通过阿里云或腾讯云一键部署的幻兽帕鲁服务器怎么更新呢&#xff1f; 腾讯云轻量云一键部署幻兽帕鲁服务器教程&#xff1a;https://curl.qcloud.com/pzBO9wN7 先来讲…

短视频去水印教程,免费一键获取视频、图片、文案【迅风去水印】

自媒体行业的蓬勃发展&#xff0c;让越来越多的创作者涌入其中。然而&#xff0c;剪辑过程中常常遭遇到一个令人头疼的问题&#xff0c;那就是视频或图片上的水印。这些水印不仅会影响到作品的美感&#xff0c;还可能侵犯到版权。为了帮大家解决这一难题&#xff0c;分享一个免…

HTML 样式学习手记

HTML 样式学习手记 在探索网页设计的世界时&#xff0c;我发现HTML元素的样式调整真的是个很酷的环节。通过简单的属性设置&#xff0c;就能让文字换上五彩斑斓的颜色、变换各异的字体和大小。特别是那个style属性&#xff0c;感觉就像是一扇通往CSS魔法世界的大门。 代码小试…

uniapp微信小程序触底加载(超简单)

你在哪个页面需要就给他在page.json里面填写以下代码&#xff0c;表示距离底部还有50px就触发 1.page.json添加以下代码 "onReachBottonDistance":50 这是文档链接 页面 | uni-app官网 (dcloud.net.cn) 2. 页面中写以下代码 onReachBottom(e) {console.log(&quo…

【Git】01 Git介绍与安装

文章目录 一、版本控制系统二、Git三、Windows安装Git3.1 下载Git3.2 安装3.3 检查 四、Linux安装Git4.1 YUM安装4.2 源码安装 五、配置Git5.1 配置用户名和邮箱5.2 配置级别5.3 查看配置 六、总结 一、版本控制系统 版本控制系统&#xff0c;Version Control System&#xff…

Rhino 8.1下载安装教程,保姆级教程,小白也能轻松搞定,附安装包和工具

前言 Rhinoceros,又叫犀牛&#xff0c;是一款小巧强大的高级三维建模工具&#xff0c;适用于机械设计、科学工业、三维动画等广泛领域。它包含了所有的NURBS建模功能&#xff0c;用它建模感觉非常流畅&#xff0c;所以大家经常用它来建模&#xff0c;然后导出高精度模型给其他…

ElementUI Form:Input 输入框

ElementUI安装与使用指南 Input 输入框 点击下载learnelementuispringboot项目源码 效果图 el-input.vue &#xff08;Input 输入框&#xff09;页面效果图 项目里el-input.vue代码 <script> export default {name: el_input,data() {return {input: ,input1: ,i…

了解 WebSocket 和 TCP :有何不同

WebSocket — 双向通讯的艺术 简要概述 WebSocket 代表着WebSocket通讯协议&#xff0c;提供了一条用于客户端和服务器间实现实时、双向、全双工通信的渠道。在WebSocket引入之前&#xff0c;网页应用的数据更新依赖于频繁的轮询&#xff0c;这种做法不仅效率低下&#xff0c;…

上位机是什么?与下位机是什么关系

在工业自动化领域中&#xff0c;上位机是一项关键而引人注目的技术。许多人对上位机的概念感到好奇&#xff0c;想要深入了解其在工业智能中的作用。那么&#xff0c;上位机究竟是什么呢&#xff1f; 首先&#xff0c;上位机是一种用于工业控制系统的软件应用&#xff0c;通常…

elementUI中表单校验的清空校验以及手动校验

this.$refs.表单.clearValidate(),这个可以传入字符串或者字符串数组&#xff0c;字符串对应的是我们自定义的rule里面的属性名&#xff0c;rule的属性名对应的是el-form-item的prop。这个api目前遇到的场景是el-radio切换时v-if展示不同的表单内容&#xff0c;但是当有校验提示…

把成绩私发给家长

与家长保持及时、有效的沟通对于学生的成长至关重要。但有时候&#xff0c;我会选择将学生的成绩私发给家长&#xff0c;而不是在公共场合公布。这样做有以下几个原因。 保护学生的隐私。每个学生都拥有自己的个人信息&#xff0c;这包括学习成绩。在公共场合公布成绩&#xf…

前后端项目

文章目录 1.需求2.项目搭建2.1项目结构图2.2构建聚合工程2.2.1 zx-parent父工程2.2.2 zx-framework父工程2.2.2.1 zx-common工程2.2.2.2 zx-mybatisplus工程2.2.3 如上,同理创建其他父子工程2.3准备sql3.用户登录3.1 修改pom-依赖其他Module3.2 封装User1.需求 在线学习平台 …

vue3-深入组件-插槽

插槽 Slots 组件用来接收模板内容 插槽内容与出口 <slot> 元素是一个插槽出口 (slot outlet),&#xff0c;标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。 插槽内容可以是任意合法的模板内容&#xff0c;不局限于文本。例如我们可以传入多个元素&#xff0…

版本管理工具git: 谨慎使用git中的撤回操作

文章目录 一、背景二、解决方案1、步骤一2、步骤二 三、参考 一、背景 昨天代码分支提交错了&#xff0c;idea中使用了如下操作&#xff0c;结果代码不见了 二、解决方案 1、步骤一 使用git reflog命令&#xff0c;查看提交记录&#xff0c;找到之前commit操作的哈希值 …

二叉树顺序结构堆实现

目录 Test.c测试代码 test1 test2 test3 &#x1f387;Test.c总代码 Heap.h头文件&函数声明 头文件 函数声明 &#x1f387;Heap.h总代码 Heap.c函数实现 ☁HeapInit初始化 ☁HeapDestroy销毁 ☁HeapPush插入数据 【1】插入数据 【2】向上调整Adjustup❗ …

Java JVM类加载阶段 双亲委派模式

类加载阶段 加载 将类的字节码载入方法区中&#xff0c;内部采用 C 的 instanceKlass 描述 java 类&#xff0c;它的重要 field 有&#xff1a; _java_mirror 即 java 的类镜像&#xff0c;例如对 String 来说&#xff0c;就是 String.class&#xff0c;作用是把 klass 暴露…