二叉搜索树题目:恢复二叉搜索树

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
      • 进阶
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法三
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:恢复二叉搜索树

出处:99. 恢复二叉搜索树

难度

5 级

题目描述

要求

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

示例

示例 1:

示例 1

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

示例 2:

示例 2

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

数据范围

  • 树中结点数目在范围 [2, 1000] \texttt{[2, 1000]} [2, 1000]
  • -2 31 ≤ Node.val ≤ 2 31 − 1 \texttt{-2}^\texttt{31} \le \texttt{Node.val} \le \texttt{2}^\texttt{31} - \texttt{1} -231Node.val2311

进阶

使用 O(n) \texttt{O(n)} O(n) 空间复杂度的解法很简单,你可以想出使用 O(1) \texttt{O(1)} O(1) 空间的解决方案吗?

解法一

思路和算法

由于二叉搜索树的中序遍历序列是单调递增的,因此可以通过中序遍历序列找到交换了值的两个结点,然后将这两个结点的值恢复。

假设二叉搜索树有 n n n 个结点,中序遍历序列是 [ x 0 , x 1 , … , x n − 1 ] [x_0, x_1, \ldots, x_{n - 1}] [x0,x1,,xn1],则对于任意 0 ≤ i < n − 1 0 \le i < n - 1 0i<n1 都有 x i < x i + 1 x_i < x_{i + 1} xi<xi+1。将交换了值的两个结点的原结点值记为 x j x_j xj x k x_k xk,其中 j < k j < k j<k,则 x j < x k x_j < x_k xj<xk。交换结点值之后的中序遍历序列中存在逆序对,即相邻的两个结点值当中,前面的值大于后面的值,在中序遍历序列中寻找逆序对的同时定位到交换了值的两个结点。

  • 如果 k − j = 1 k - j = 1 kj=1,即 x j x_j xj x k x_k xk 在中序遍历序列中相邻,则只有这两个结点值在交换之后产生一个逆序对,因此当中序遍历序列中存在一个逆序对时,逆序对的两个值对应的结点即为交换了值的两个结点。

  • 如果 k − j > 1 k - j > 1 kj>1,即 x j x_j xj x k x_k xk 在中序遍历序列中不相邻,则这两个结点值在交换之后分别产生一个逆序对,即 x k > x j + 1 x_k > x_{j + 1} xk>xj+1 x k − 1 > x j x_{k - 1} > x_j xk1>xj,因此当中序遍历序列中存在两个逆序对时,第一个逆序对的前一个结点和第二个逆序对的后一个结点即为交换了值的两个结点。

在定位到交换了值的两个结点之后,将这两个结点的值交换,即恢复了二叉搜索树。

具体做法是,首先对给定的二叉搜索树(在交换两个结点值之后)中序遍历,中序遍历序列中存储结点,然后遍历中序遍历序列,统计逆序对的数量以及定位到交换了值的两个结点,遍历结束之后,将两个结点的值交换,完成二叉搜索树的恢复。

代码

下面的代码为递归实现二叉搜索树中序遍历的做法。

class Solution {List<TreeNode> traversal = new ArrayList<TreeNode>();public void recoverTree(TreeNode root) {inorder(root);int index1 = -1, index2 = -1;int size = traversal.size();for (int i = 1; i < size; i++) {if (traversal.get(i).val < traversal.get(i - 1).val) {if (index1 < 0) {index1 = i - 1;} else {index2 = i;}}}TreeNode node1 = null, node2 = null;if (index2 < 0) {node1 = traversal.get(index1);node2 = traversal.get(index1 + 1);} else {node1 = traversal.get(index1);node2 = traversal.get(index2);}int val1 = node1.val, val2 = node2.val;node1.val = val2;node2.val = val1;}public void inorder(TreeNode node) {if (node == null) {return;}inorder(node.left);traversal.add(node);inorder(node.right);}
}

下面的代码为迭代实现二叉搜索树中序遍历的做法。

class Solution {public void recoverTree(TreeNode root) {List<TreeNode> traversal = new ArrayList<TreeNode>();Deque<TreeNode> stack = new ArrayDeque<TreeNode>();TreeNode node = root;while (!stack.isEmpty() || node != null) {while (node != null) {stack.push(node);node = node.left;}node = stack.pop();traversal.add(node);node = node.right;}int index1 = -1, index2 = -1;int size = traversal.size();for (int i = 1; i < size; i++) {if (traversal.get(i).val < traversal.get(i - 1).val) {if (index1 < 0) {index1 = i - 1;} else {index2 = i;}}}TreeNode node1 = null, node2 = null;if (index2 < 0) {node1 = traversal.get(index1);node2 = traversal.get(index1 + 1);} else {node1 = traversal.get(index1);node2 = traversal.get(index2);}int val1 = node1.val, val2 = node2.val;node1.val = val2;node2.val = val1;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。中序遍历需要访问每个结点一次,需要 O ( n ) O(n) O(n) 的时间,交换结点值需要 O ( 1 ) O(1) O(1) 的时间,因此时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。中序遍历的递归实现和迭代实现都需要栈空间,栈空间取决于二叉搜索树的高度,最坏情况下二叉搜索树的高度是 O ( n ) O(n) O(n),存储中序遍历序列需要 O ( n ) O(n) O(n) 的空间。

解法二

思路和算法

由于只需要定位到交换了值的两个结点,因此并不需要得到完整的中序遍历序列,只要能确定交换了值的两个结点,即可结束遍历。

中序遍历的过程中,如果遇到一个逆序对,则无法确定是否还有第二个逆序对,只有当遇到第二个逆序对时,才能确定交换了值的两个结点。因此,当第二次遇到逆序对时,定位到交换了值的两个结点,即可提前退出。

定位到交换了值的两个结点之后,将两个结点的值交换,完成二叉搜索树的恢复。

代码

class Solution {public void recoverTree(TreeNode root) {Deque<TreeNode> stack = new ArrayDeque<TreeNode>();TreeNode prev = null, curr = root;TreeNode node1 = null, node2 = null;while (!stack.isEmpty() || curr != null) {while (curr != null) {stack.push(curr);curr = curr.left;}curr = stack.pop();if (prev != null && curr.val < prev.val) {node2 = curr;if (node1 == null) {node1 = prev;} else {break;}}prev = curr;curr = curr.right;}int val1 = node1.val, val2 = node2.val;node1.val = val2;node2.val = val1;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。每个结点最多被访问一次。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。空间复杂度主要是栈空间,取决于二叉搜索树的高度,最坏情况下二叉搜索树的高度是 O ( n ) O(n) O(n)

解法三

思路和算法

莫里斯遍历是使用常数空间遍历二叉树的方法,使用莫里斯遍历对二叉搜索树中序遍历,可将空间复杂度降低到 O ( 1 ) O(1) O(1)

莫里斯中序遍历的过程中,统计逆序对的数量以及定位到交换了值的两个结点,遍历结束之后,将两个结点的值交换,完成二叉搜索树的恢复。

由于莫里斯遍历的过程中会改变树的结构,只有当遍历结束时才能确保树的结构恢复,因此莫里斯遍历不能提前退出。

代码

class Solution {public void recoverTree(TreeNode root) {TreeNode prev = null, curr = root;TreeNode node1 = null, node2 = null;while (curr != null) {if (curr.left == null) {if (prev != null && curr.val < prev.val) {if (node1 == null) {node1 = prev;}node2 = curr;}prev = curr;curr = curr.right;} else {TreeNode predecessor = curr.left;while (predecessor.right != null && predecessor.right != curr) {predecessor = predecessor.right;}if (predecessor.right == null) {predecessor.right = curr;curr = curr.left;} else {predecessor.right = null;if (prev != null && curr.val < prev.val) {if (node1 == null) {node1 = prev;}node2 = curr;}prev = curr;curr = curr.right;}}}int val1 = node1.val, val2 = node2.val;node1.val = val2;node2.val = val1;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。使用莫里斯遍历,每个结点最多被访问两次。

  • 空间复杂度: O ( 1 ) O(1) O(1)

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

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

相关文章

西门子PLC常用底层逻辑块分享_单/双输出电机

文章目录 前言一、功能概述二、单输出电机程序编写1.创建自定义数据类型2.创建FB功能块“单输出电机”3.编写程序 三、双输出电机程序编写1.创建自定义数据类型2.创建FB功能块“双输出电机”3.编写程序 前言 本文分享一个自己编写的电机控制逻辑块。 一、功能概述 手动状态、…

2024年1月粮油调味行业分析(TOP品牌/店铺/商品销售数据分析)

鲸参谋监测的某东1月份粮油调味市场销售数据已出炉&#xff01; 根据鲸参谋电商数据分析平台显示&#xff0c;今年1月份&#xff0c;某东平台上粮油调味品的销量约6200万件&#xff0c;环比上个月增长45%&#xff0c;同比去年下滑15%&#xff1b;销售额约25亿元&#xff0c;环…

Android 监听卫星导航系统状态及卫星测量数据变化

源码 package com.android.circlescalebar;import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.conte…

后渗透--利用ebpf隐藏后门用户

原理 首先我们要搞清楚ssh登陆的流程 先获取到ssh的pid 然后利用strace strace -f -p 830可以看到他打开了/etc/passwd去读取内容 那么我们的思路就很简单&#xff0c;hook ssh进程的read syscall exit,然后篡改返回内容 代码 ebpf // build ignore#include "my…

LeetCode2115. 从给定原材料中找到所有可以做出的菜

拓扑排序 题面 题目链接&#xff1a;2115. 从给定原材料中找到所有可以做出的菜 - 力扣&#xff08;LeetCode&#xff09; 你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。第 i 道菜的名字为 recipes[i] &#xff0c;如果你有它 所有…

html5cssjs代码 018颜色表

html5&css&js代码 018颜色表 一、代码二、效果三、解释 这段代码展示了一个基本的颜色表&#xff0c;方便参考使用&#xff0c;同时也应用了各种样式应用方式。 一、代码 <!DOCTYPE html> <html lang"zh-cn"> <head><title>编程笔记…

速卖通安全测评补单技术提升运营安全性

对于一个新品来说&#xff0c;最大的问题就是评论。没有评论&#xff0c;你的广告就不能打的很靠前&#xff0c;那样你的转化率就会非常低&#xff0c;数据也很差。新品运气不好的来两个一星差评&#xff0c;链接可能就此废掉&#xff0c;做不上去了。所以虽然平台管的非常的严…

智能工厂核心功能系统-MES生产管理系统

MES在未来智能制造中扮演着至关重要的角色&#xff0c;通过其在生产管理中的应用&#xff0c;将帮助企业实现智能化转型&#xff0c;提升生产效率和产品质量&#xff0c;推动整个制造业向着更加智能、高效、可持续的方向发展。 通过对MES在未来智能制造发展趋势中的地位进行深…

分布式系统互斥性与幂等性问题的分析解决

前言 随着互联网信息技术的飞速发展&#xff0c;数据量不断增大&#xff0c;业务逻辑也日趋复杂&#xff0c;对系统的高并发访问、海量数据处理的场景也越来越多。 如何用较低成本实现系统的高可用、易伸缩、可扩展等目标就显得越发重要。为了解决这一系列问题&#xff0c;系…

Redis持久化和集群

redis持久化 RDB方式 Redis Database Backup file (redis数据备份文件), 也被叫做redis数据快照. 简单来说就是把内存中的所有数据记录到磁盘中. 快照文件称为RDB文件, 默认是保存在当前运行目录. [rootcentos-zyw ~]# docker exec -it redis redis-cli 127.0.0.1:6379> sav…

一个八年工作经验老程序员的分享

作为一个 Java 程序员&#xff0c;我在这个行业中工作了多年。在这个过程中&#xff0c;我经历了许多挑战和机遇&#xff0c;也学到了很多宝贵的经验和教训。在这篇文章中&#xff0c;我想分享一些我的感想和思考&#xff0c;希望能够对其他 Java 程序员有所帮助。 一、技术的…

Go语言简介

一.Go语言简介 1.1 优点 自带gc静态编译&#xff0c;编译好后&#xff0c;扔服务器直接运行简单思想&#xff0c;没有继承&#xff0c;多态和类等丰富的库和详细开发文档语法层支持并发&#xff0c;和拥有同步并发的channel类型&#xff0c;使并发开发变得非常方便简洁语法&am…

JAVA 服务可观测性最佳实践

前言 本次实践主要是介绍 Java 服务通过无侵入的方式接入观测云进行全面的可观测。 环境信息 系统环境&#xff1a;Ubuntu&#xff08;主机环境&#xff09;开发语言&#xff1a;JDK 11.0.18Web 框架&#xff1a;SpringBoot日志框架&#xff1a;LogbackAPM 探针&#xff1a;…

【linux深入剖析】操作系统与用户之间的接口:自定义简易shell制作全过程

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1.shell2.自定义shell的准…

x86_64架构栈帧以及帧指针FP

文章目录 一、x86_64架构寄存器简介二、x86_64架构帧指针FP三、示例四、保存帧指针参考资料 一、x86_64架构寄存器简介 在x86架构中&#xff0c;有8个通用寄存器可用&#xff1a;eax、ebx、ecx、edx、ebp、esp、esi和edi。在x86_64&#xff08;x64&#xff09;扩展中&#xff…

第八届蓝桥杯省赛 分巧克力(二分)

题目描述&#xff1a; 思路&#xff1a; 给出N个长方形的长和宽&#xff0c;可以分别看长能被分成多少块&#xff0c;宽能被分为多少块&#xff0c; 也就是 (h/mid) * (w/mid),使其大于等于K 所以我们可以通过二分去找&#xff0c;最大的边长是多少 AC代码&#xff1a; #inc…

深度学习技巧总结

1、监控GPU使用情况 pip install nvitopnvitop -m fullhttps://zhuanlan.zhihu.com/p/577533593 2、本地拉取服务器上tensorboard数据并进行可视化显示 https://blog.csdn.net/Thebest_jack/article/details/125609849 3、服务器打不开pycharm软件 这个是已经有一个软件在运…

SD-WAN解决企业云网融合问题

随着市场竞争不断加剧&#xff0c;企业在提升业务的同时也面临着新兴业务需求的涌现。数字化发展的关键路径包括上云、跨云、云迁移&#xff0c;而广域网连接已不再仅限于总部和分支机构之间。为应对企业云转型对网络架构提出的更高要求&#xff0c;SD-WAN成为企业解决云网融合…

【SpringBoot】自定义工具类实现Excel数据新建表存入MySQL数据库

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

linux centos系统搭建samba文件服务器 NetBIOS解析 (超详细)

CSDN 成就一亿技术人&#xff01; 作者主页&#xff1a;点击&#xff01; Linux专栏&#xff1a;点击&#xff01; CSDN 成就一亿技术人&#xff01; 前言———— Samba 是一个开源软件套件&#xff0c;可为 SMB/CIFS 客户端&#xff08;包括 Windows&#xff09;提供文件…