题目来源

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

进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?

示例1:

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

示例2:

  1. 输入:root = [3,1,4,null,null,2]
  2. 输出:[2,1,4,null,null,3]
  3. 解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 3 使二叉搜索树有效。
  1. 来源:力扣(LeetCode
  2. 链接:https://leetcode-cn.com/problems/recover-binary-search-tree
  3. 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目解析

什么意思呢?这是其实是两道题,第一道是基础的,就是用基本的解法即可,关键是第二种如何优化你的算法。

好,我们先说第一种,大众思维。

既然题目上说了错误地交换了搜索二叉树(BST)的两个节点,那么BST又有什么特点呢?

我们知道中序遍历搜索BST会得到一组升序的数组(比如:[1,2,3,4,5,6,7,8]),那好,按照题意我们交换两组节点2和6,数组变成[1,6,3,4,5,2,7,8],此时 可发现数组的升序被打破了,因为6>3,5>2。没错,我们就利用该性质是不是就可以找出交换的两个节点位置,然后做交换就好了?

解析方法归纳:

1、先得到BST中序遍历的数组序列;

2、找到不满足条件的位置;

3、看节点数有几个:

  3.1、如果有两个,即[1,6,3,4,5,2,7,8]中6>3,5>2,那么就将位置分别记为 i 和 j(i < j,其中i是6,j是5,特别提醒并不是2哦),对应的交换错的节点为 Ai 和 Aj+1 (Ai > Ai+1 && Aj > Aj+1),我们分别记为x,y;

  3.2、如果有一个,即[1,2,3,5,4,6,7,8]中的5>4,那么就将位置记为 i ,交换错的位置就是Ai 和 Ai+1,我们分别记为x,y;

4、遍历树,交换节点 x , y 。

好了。思路也很清楚了,关键是如何实现。

第一步:先序遍历BST这应该挺简单的,递归嘛,我们将数组定位为nums来记录:

  1. //c++
void inOrder(TreeNode * root, vector<int>& nums){
    if(root == nullptr){
        return;
    }
    inOrder(root->left, nums);
    nums.push_back(root->val);
    inOrder(root->right, nums);
}

第二步:找到不满足条件的位置;但是该位置可能有一个,也可能有两个,所以,得要遍历数组一次。

  1. vector<int> find2val(TreeNode* root, vector<int>& nums){
  2. int n1 = 0;
  3. int n2 = 0;
  4. bool sec = false;
  5. for(int i = 0; i<nums.size()-1; i++){
  6. if(nums[i]>nums[i+1]){
  7. if(!sec){
  8. n1 = nums[i];
  9. n2 = nums[i+1];
  10. sec = true;
  11. }
  12. else
  13. n2 = nums[i+1];
  14. }
  15. }
  16. return {n1,n2};
  17. }

第三步:看数组中到底有几次

我们这里在主函数中直接就写成2了,因为最多为两次,当然也可以将这个次数记录下来;

第四步:遍历树,换位置

  1. void reverse(TreeNode * root, int count, int x, int y){
  2. if(root!=nullptr){
  3. if(root->val == x || root->val == y){
  4. root->val = (root->val == x) ? y : x;//swap (x,y)
  5. if(--count == 0){//来计数是第几次如果是第二次了后面的就不用再遍历了;
  6. return;
  7. }
  8. }
  9. reverse(root->left,count,x,y);
  10. reverse(root->right,count,x,y);
  11. }
  12. }

第五步:主函数

  1. void recoverTree(TreeNode* root) {
  2. vector<int> nums;
  3. inOrder(root, nums);//第一步中序遍历得到升序数组
  4. vector<int> swap_vals = find2val(root, nums);//找到两个被错误交换的值
  5. reverse(root,2,swap_vals[0],swap_vals[1]);//遍历树,进行交换;
  6. }

算法分析:

  • 时间复杂度:O(N),其中N为BST的节点数。中序遍历要O(N)的时间,而判断交换节点在哪里,最好的情况是O(1),最坏的情况是O(N),所以是O(N);
  • 空间复杂度:O(N),因为用到了一个数组来存放升序数列;

以上是一般大众思维,那么如何进行优化呢?优化的点在哪里呢?

其实,我们没有必要去引入这个nums数组,因为我们在中序遍历树时,如果去维护一个前节点变量,那么我们就可以在遍历过程中直接进行比较,我们在这里引入一个栈,并且迭代实现中序遍历,并不是递归。具体用法看下面;

如:

  1. 3
  2. / \
  3. 1 4
  4. /
  5. 2

第一步:中序遍历,先找到最左节点,中途所有的节点都入栈;

第二步:继续;

第三步:取栈顶元素,并赋予前一个节点变量pred,并向弹出的节点的右子树走;

第四步:继续,因为1的右节点,也是NULL,故继续弹栈,弹出来也就是1的父节点3;赋予前一个节点变量pred=3;

第五步:遍历右子树,有值,寻找右子树中的最左节点,并将沿途所遍历的节点都入栈;

第六步:入栈,找到右子树的最左节点;

第七步:弹出栈顶,此时相当于,找到了以 3 为根节点的,中序遍历中左子树的最后一个节点值,和右子树中的第一个值。这句话的意思是,当你中序遍历时,3的前后值分别为 1 和 2 ;

看好这里,发现了前后节点值大小异常:2 < 3,记录下这两个节点Node1,Node2;

第八步:继续;

第九步:遍历完毕,找到了交换的点Node1,Node2,进行交换即可;

 代码实现:

  1. class Solution {
  2. public:
  3. void recoverTree(TreeNode* root) {
  4. stack<TreeNode*> stk;
  5. TreeNode* x = nullptr;
  6. TreeNode* y = nullptr;
  7. TreeNode* pred = nullptr;
  8.  
  9. while (!stk.empty() || root != nullptr) {
  10. while (root != nullptr) {
  11. stk.push(root);
  12. root = root->left;
  13. }
  14. root = stk.top();
  15. stk.pop();
  16. if (pred != nullptr && root->val < pred->val) {
  17. y = root;
  18. if (x == nullptr) {
  19. x = pred;
  20. }
  21. else break;
  22. }
  23. pred = root;
  24. root = root->right;
  25. }
  26.  
  27. swap(x->val, y->val);
  28. }
  29. };

这部分图来自:

  1. 作者:LeetCode-Solution
  2. 链接:https://leetcode-cn.com/problems/recover-binary-search-tree/solution/hui-fu-er-cha-sou-suo-shu-by-leetcode-solution/
  3. 来源:力扣(LeetCode
  4. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析:

  • 时间复杂度:最坏情况下是需要遍历整棵树(即交换节点为BST的最右侧的两个节点),时间复杂度为O(N),N为节点个数;
  • 空间复杂度:O(H),H为BST的高度;注意:中序遍历的时候,栈的深度取决于树的高度噢!!!

亲爱的,你们以为到这里就结束了吗?

错,大错特错,在这里突然冒出一个Morris中序遍历算法,这个算法之前是真的不知道。无知了...

图解算法——恢复一棵二叉搜索树(BST)的更多相关文章

  1. C++版 - 剑指offer 面试题24:二叉搜索树BST的后序遍历序列(的判断) 题解

    剑指offer 面试题24:二叉搜索树的后序遍历序列(的判断) 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true.否则返回false.假设输入的数组的任意两个 ...

  2. 【算法与数据结构】二叉搜索树的Java实现

    为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨. 首先,二叉搜索树是啥?它有什么用呢? 二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指 ...

  3. [LeetCode] Trim a Binary Search Tree 修剪一棵二叉搜索树

    Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that a ...

  4. PAT 天梯赛 是否同一棵二叉搜索树   (25分)(二叉搜索树 指针)

    给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到.例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果 ...

  5. PTA L2-004 这是二叉搜索树吗?-判断是否是对一棵二叉搜索树或其镜像进行前序遍历的结果 团体程序设计天梯赛-练习集

    L2-004 这是二叉搜索树吗? (25 分)   一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点, 其左子树中所有结点的键值小于该结点的键值: 其右子树中所有结点的键值大于等于该结 ...

  6. PTA 是否同一棵二叉搜索树(25 分)

    是否同一棵二叉搜索树(25 分) 给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到.例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始 ...

  7. 04-树4 是否同一棵二叉搜索树(25 point(s)) 【Tree】

    04-树4 是否同一棵二叉搜索树(25 point(s)) 给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到.例如分别按照序列{2, 1, 3}和 ...

  8. PTA 04-树4 是否同一棵二叉搜索树 (25分)

    题目地址 https://pta.patest.cn/pta/test/15/exam/4/question/712 5-4 是否同一棵二叉搜索树   (25分) 给定一个插入序列就可以唯一确定一棵二 ...

  9. 7-4 是否同一棵二叉搜索树 (25分) JAVA

    给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到. 例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结 ...

随机推荐

  1. 主题模型值LDA

    主题模型(topic model)是以非监督学习的方式对文集的隐含语义结构(latent semantic structure)进行聚类(clustering)的统计模型. 主题模型主要被用于自然语言 ...

  2. spring mvc + mybaties + mysql 完美整合cxf 实现webservice接口 (服务端、客户端)

    spring-3.1.2.cxf-3.1.3.mybaties.mysql 整合实现webservice需要的完整jar文件 地址:http://download.csdn.net/detail/xu ...

  3. 卷积神经网络学习笔记——SENet

    完整代码及其数据,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/DeepLearningNote 这里结合网络的资料和SE ...

  4. linux 文件目录权限

    文件目录权限: 什么是文件权限: 在Linux中,每个文件都有所属的所有者,和所有组,并且规定了文件的所有者,所有组以及其他人对文件的,可读,可写,可执行等权限. 对于目录的权限来说,可读是读取目录文 ...

  5. Java面试官经验谈:如何甄别候选人真实的能力,候选人如何展示值钱技能

    我做Java方面的面试官也有些年头了,从校招学生到初级开发到架构师我都面试过.从技术上来讲,候选人通过面试的标准可能千差万别,但归结成一句话,就是候选人达到了职位介绍的要求,且相关项目经验达到足量的年 ...

  6. 【IDEA】Lombok--是否值得我们去使用

    官网 https://projectlombok.org/ 简介 Project Lombok is a java library that automatically plugs into your ...

  7. Cisco之show基础命令

    #show  version:显示版本信息等 #show running-config:显示当前(活动,并不一定保存)的配置 #show interfaces fastEthernet 0/1:进入接 ...

  8. .net core 不同地区时间相互转换

    .net core 不同地区时间相互转换 //韩国时间转换成当前时间 //value=需要转换的时间 //Korea Standard Tim 韩国时间 //China Standard Time 中 ...

  9. OAuth2.0是干什么的?

    OAuth2.0是干什么的? 首先用户有一些数据: 将数据存储在服务器上: 这时候有一个应用要访问数据: 如果这个应用是一个恶意程序呢?所以需要一个检验来判断请求是不是安全的: 如何判断是不是安全的? ...

  10. 我教你如何解决 Docker 下载 mcr.microsoft.com 镜像慢的办法

    我教你如何解决 Docker 下载 mcr.microsoft.com 镜像慢的办法 一.介绍 最近,我在写有关使用 Jenkins 搭建企业级持续集成环境的文章,准备了四台服务器,企业级别嘛,一台就 ...