【leetcode热题100】恢复二叉搜索树

给你二叉搜索树的根节点 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 使二叉搜索树有效。

解法一 递归

和 98 题有些像。这里的思路如下:

让我们来考虑交换的位置的可能:

  1. 根节点和左子树的某个数字交换 -> 由于根节点大于左子树中的所有数,所以交换后我们只要找左子树中最大的那个数,就是所交换的那个数

  2. 根节点和右子树的某个数字交换 -> 由于根节点小于右子树中的所有数,所以交换后我们只要在右子树中最小的那个数,就是所交换的那个数

  3. 左子树和右子树的两个数字交换 -> 找左子树中最大的数,右子树中最小的数,即对应两个交换的数
  4. 左子树中的两个数字交换
  5. 右子树中的两个数字交换

思想有了,代码很好写了。

public void recoverTree2(TreeNode root) {if (root == null) {return;}//寻找左子树中最大的节点TreeNode maxLeft = getMaxOfBST(root.left);//寻找右子树中最小的节点TreeNode minRight = getMinOfBST(root.right);if (minRight != null && maxLeft != null) {//左边的大于根节点,右边的小于根节点,对应情况 3,左右子树中的两个数字交换if ( maxLeft.val > root.val && minRight.val < root.val) {int temp = minRight.val;minRight.val = maxLeft.val;maxLeft.val = temp;}}if (maxLeft != null) {//左边最大的大于根节点,对应情况 1,根节点和左子树的某个数做了交换if (maxLeft.val > root.val) {int temp = maxLeft.val;maxLeft.val = root.val;root.val = temp;}}if (minRight != null) {//右边最小的小于根节点,对应情况 2,根节点和右子树的某个数做了交换if (minRight.val < root.val) {int temp = minRight.val;minRight.val = root.val;root.val = temp;}}//对应情况 4,左子树中的两个数进行了交换recoverTree(root.left);//对应情况 5,右子树中的两个数进行了交换recoverTree(root.right);}
//寻找树中最小的节点
private TreeNode getMinOfBST(TreeNode root) {if (root == null) {return null;}TreeNode minLeft = getMinOfBST(root.left);TreeNode minRight = getMinOfBST(root.right);TreeNode min = root;if (minLeft != null && min.val > minLeft.val) {min = minLeft;}if (minRight != null && min.val > minRight.val) {min = minRight;}return min;
}//寻找树中最大的节点
private TreeNode getMaxOfBST(TreeNode root) {if (root == null) {return null;}TreeNode maxLeft = getMaxOfBST(root.left);TreeNode maxRight = getMaxOfBST(root.right);TreeNode max = root;if (maxLeft != null && max.val < maxLeft.val) {max = maxLeft;}if (maxRight != null && max.val < maxRight.val) {max = maxRight;}return max;
}

解法二

参考 这里。

如果记得 98 题,我们判断是否是一个合法的二分查找树是使用到了中序遍历。原因就是二分查找树的一个性质,左孩子小于根节点,根节点小于右孩子。所以做一次中序遍历,产生的序列就是从小到大排列的有序序列。

回到这道题,题目交换了两个数字,其实就是在有序序列中交换了两个数字。而我们只需要把它还原。

交换的位置的话就是两种情况。

  • 相邻的两个数字交换

    [ 1 2 3 4 5 ] 中 2 和 3 进行交换,[ 1 3 2 4 5 ],这样的话只产生一组逆序的数字(正常情况是从小到大排序,交换后产生了从大到小),3 2。

    我们只需要遍历数组,找到后,把这一组的两个数字进行交换即可。

  • 不相邻的两个数字交换

    [ 1 2 3 4 5 ] 中 2 和 5 进行交换,[ 1 5 3 4 2 ],这样的话其实就是产生了两组逆序的数字对。5 3 和 4 2。

    所以我们只需要遍历数组,然后找到这两组逆序对,然后把第一组前一个数字和第二组后一个数字进行交换即完成了还原。

所以在中序遍历中,只需要利用一个 pre 节点和当前节点比较,如果 pre 节点的值大于当前节点的值,那么就是我们要找的逆序的数字。分别用两个指针 first 和 second 保存即可。如果找到第二组逆序的数字,我们就把 second 更新为当前节点。最后把 first 和 second 两个的数字交换即可。

中序遍历,参考 94 题 ,有三种方法,递归,栈,Morris 。这里的话,我们都改一下。

1. 递归版中序遍历

TreeNode first = null;
TreeNode second = null;
public void recoverTree(TreeNode root) {inorderTraversal(root);int temp = first.val;first.val = second.val;second.val = temp;
}
TreeNode pre = null;
private void inorderTraversal(TreeNode root) {if (root == null) {return;}inorderTraversal(root.left); /*******************************************************/if(pre != null && root.val < pre.val) {//第一次遇到逆序对if(first==null){first = pre;second = root;//第二次遇到逆序对}else{second = root;}}pre = root; /*******************************************************/inorderTraversal(root.right);
}

2. 栈版中序遍历

TreeNode first = null;
TreeNode second = null;public void recoverTree(TreeNode root) {inorderTraversal(root);int temp = first.val;first.val = second.val;second.val = temp;
}public void inorderTraversal(TreeNode root) {if (root == null)return;Stack<TreeNode> stack = new Stack<>();TreeNode pre = null;while (root != null || !stack.isEmpty()) {while (root != null) {stack.push(root);root = root.left;}root = stack.pop();/*******************************************************/if (pre != null && root.val < pre.val) {if (first == null) {first = pre;second = root;} else {second = root;}}pre = root;/*******************************************************/root = root.right;}
}

3. Morris 版中序遍历

因为之前这个方法中用了 pre 变量,为了方便,这里也需要 pre 变量,我们用 pre_new 代替。具体 Morris 遍历算法参见 94 题 。利用 Morris 的话,我们的空间复杂度终于达到了 O(1)。

public void recoverTree(TreeNode root) {TreeNode first = null;TreeNode second = null;TreeNode cur = root;TreeNode pre_new = null;while (cur != null) {// 情况 1if (cur.left == null) {/*******************************************************/if (pre_new != null && cur.val < pre_new.val) {if (first == null) {first = pre_new;second = cur;} else {second = cur;}}pre_new = cur;/*******************************************************/cur = cur.right;} else {// 找左子树最右边的节点TreeNode pre = cur.left;while (pre.right != null && pre.right != cur) {pre = pre.right;}// 情况 2.1if (pre.right == null) {pre.right = cur;cur = cur.left;}// 情况 2.2if (pre.right == cur) {pre.right = null; // 这里可以恢复为 null/*******************************************************/if (pre_new != null && cur.val < pre_new.val) {if (first == null) {first = pre_new;second = cur;} else {second = cur;}}pre_new = cur;/*******************************************************/cur = cur.right;}}}int temp = first.val;first.val = second.val;second.val = temp;
}

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

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

相关文章

【C深度解剖】取模与取余

简介&#xff1a;本系列博客为C深度解剖系列内容&#xff0c;以某个点为中心进行相关详细拓展 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…

HiveSQL——用户行为路径分析

注&#xff1a;参考文档&#xff1a; SQL之用户行为路径分析--HQL面试题46【拼多多面试题】_路径分析 sql-CSDN博客文章浏览阅读2k次&#xff0c;点赞6次&#xff0c;收藏19次。目录0 问题描述1 数据分析2 小结0 问题描述已知用户行为表 tracking_log&#xff0c; 大概字段有&…

ELAdmin 发送邮件

邮箱配置 ELAdmin目录中选择系统工具->邮件工具。 发件人邮箱&#xff1a;发送者的邮箱地址发件用户名&#xff1a;一般都是发件人邮箱前面的部分&#xff0c;也可以任意写邮箱密码&#xff1a;如果是 qq 邮箱或者腾讯企业邮箱&#xff0c;需要使用授权码。SMTP地址&…

CSRNET图像修复,DNN

CSRNET图像修复 CSRNET图像修复&#xff0c;只需要OPENCV的DNN

【汇编】简单的linux汇编语言程序

一、Linux系统汇编语言 Linux系统上的汇编语言可以使用不同的语法风格&#xff0c;主要包括Intel语法和AT&T语法。这两种语法有各自的特点和风格区别&#xff0c;尽管它们表示的底层机器指令相同。下面分别对两种语法进行简要说明&#xff1a; Intel语法 Intel语法是由I…

C语言------一种思路解决实际问题

1.比赛名次问题 ABCDE参加比赛&#xff0c;那么每个人的名次都有5种可能&#xff0c;即1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff1b; int main() {int a 0;int b 0;int c 0;int d 0;int e 0;for (a 1; a < 5; a){for (b 1; b < 5; b){for…

Panalog 日志审计系统 libres_syn_delete.php 前台RCE漏洞复现

0x01 产品简介 Panalog是一款日志审计系统,方便用户统一集中监控、管理在网的海量设备。 0x02 漏洞概述 Panalog日志审计系统 libres_syn_delete.php接口处存在远程命令执行漏洞,攻击者可执行任意命令,接管服务器权限。 0x03 影响范围 version <= MARS r10p1Free 0…

2024.2.10 DMS(数据库管理系统)初体验

数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件&#xff0c;用于建立、使用和维护数据库&#xff0c;简称DBMS。它对数据库进行统一的管理和控制&#xff0c;以保证数据库的安全性和完整性。用户通过DBMS访问数据库中的数据&#xff0c;数据库管…

OpenCV 笔记(22):图像的缩放——最近邻插值、双线性插值算法

1. 图像缩放 1.1 简介 图像缩放是指通过增加或减少像素来改变图像尺寸的过程&#xff0c;是图像处理中常见的操作。图像缩放会涉及效率和图像质量之间的权衡。 图像放大&#xff08;也称为上采样或插值&#xff09;的主要目的是放大原图像&#xff0c;以便在更高分辨率的显示设…

springboot集成elasticsearch

一、依赖下载 创建好一个springboot项目&#xff0c;需要集成es&#xff1a; 因为springboot默认集成了es&#xff0c;但是版本号需要与本地或者服务器es的版本号一致&#xff0c;我本地es版本是7.14.0&#xff0c;所以需要在<properties></properties>中指定es版…

插值(一)——多项式插值(C++)

插值 插值的作用是可以将原本比较难计算的函数转换为误差在一定范围内的多项式&#xff0c;比如在单片机中直接计算 x 、 log ⁡ 2 x \sqrt{x}、\log_2x x ​、log2​x之类的函数是比较麻烦的&#xff0c;但是使用插值的方法就可以将其转换为误差可控的只有乘法和加减法的多项…

【机器学习案例4】为机器学习算法编码分类数据【含源码】

目录 编码分类数据 序数编码 标签编码 一次性编码 目标编码 目标编码的优点 目标编码的缺点 在现实生活中,收集的原始数据很少采用我们可以直接用于机器学习模型的格式,即数值型数据。因此,需要进行一些预处理,以便以正确的格式呈现数据、选择信息丰富的数据或降低其…

VitePress-12-markdown中使用vue的语法

前言 VitePress 中&#xff0c;markdown文档最终都会转换成为 html文件&#xff0c;我们在访问的时候&#xff0c;也是直接访问的 xxx.html 文件。而且&#xff0c;markdown文档会被作为 [vue单文件] 进行处理&#xff0c;因此&#xff0c;我们我们可以在文档中使用 vue 语法&…

C++ new 和 malloc 的区别?

相关系列文章 C new 和 malloc 的区别&#xff1f; C内存分配策略​​​​​​​ 目录 1.引言 2.区别 2.1.申请的内存分配区域 2.2.类型安全和自动大小计算 2.3.构造函数和析构函数的调用 2.4.异常处理 2.5.配对简便性 2.6.new 的重载 2.7.关键字和操作符 3.总结 1.引…

WebSocket原理详解

目录 1.引言 1.1.使用HTTP不断轮询 1.2.长轮询 2.websocket 2.1.概述 2.2.websocket建立过程 2.3.抓包分析 2.4.websocket的消息格式 3.使用场景 4.总结 1.引言 平时我们打开网页&#xff0c;比如购物网站某宝。都是点一下列表商品&#xff0c;跳转一下网页就到了商品…

OpenGL-ES 学习(4)---- OpenGL-ES 坐标体系

坐标体系 我们知道 OpenGL -ES 坐标系中每个顶点的 x&#xff0c;y&#xff0c;z 坐标都应该在 -1.0 到 1.0 之间&#xff0c;超出这个坐标范围的顶点都将不可见。 将一个物体&#xff08;图像&#xff09;渲染到屏幕上&#xff0c;通常经过将物体坐标转换为标准化设备坐标&am…

高德地图上绘制热力图的方法

百度地图和高德地图的JavaScript API都提供了热力图的绘制方法&#xff0c;都是将热力图作为新的图层&#xff0c;叠加到地图上。但是百度地图的经纬度体系与我们的经纬度存在偏差&#xff0c;高德的与我们相符&#xff0c;应当使用高德地图JavaScript API。 因为是JavaScript…

Elasticsearch:特定领域的生成式 AI - 预训练、微调和 RAG

作者&#xff1a;来自 Elastic Steve Dodson 有多种策略可以将特定领域的知识添加到大型语言模型 (LLM) 中&#xff0c;并且作为积极研究领域的一部分&#xff0c;正在研究更多方法。 对特定领域数据集进行预训练和微调等方法使 LLMs 能够推理并生成特定领域语言。 然而&#…

Mysql的安装、使用、优势与教程

一.安装 1.在小皮的设置界面检测3306端口&#xff0c;保障3306端口可用&#xff1b; 2、在小皮的首面界面&#xff0c;启动MySQL&#xff1b; 3、进行环境变量设置&#xff0c;找到MySQL的路径&#xff0c;进行复制&#xff1b; 4、在Windows的搜索栏内&#xff0c;输入“环境…

Linux 驱动开发基础知识——总线设备驱动模型(七)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…