• 【LeetCode】恢复二叉搜索树 [M](Morris遍历)


    99. 恢复二叉搜索树 - 力扣(LeetCode)

    一、题目

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

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

    示例 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 使二叉搜索树有效。

    提示:

    • 树上节点的数目在范围 [2, 1000] 内
    • -231 <= Node.val <= 231 - 1

    二、代码

    方法一(时间复杂度O(N),额外空间复杂度O(树的高度)) 

    1. class Solution {
    2. // 时间复杂度O(N),空间复杂度O(树的高度)
    3. public void recoverTree(TreeNode root) {
    4. // 用来记录所需要的两个降序位置
    5. // ans[0]:表示第一次降序的第一个节点
    6. // ans[1]:表示最后一次降序的第二个节点(如果只有一次降序,那么这个就是唯一这一次降序的第二个节点)
    7. // 通过中序序列得到我们需要的降序位置
    8. TreeNode[] ans = in(root);
    9. TreeNode s1;
    10. TreeNode s2;
    11. // 将找到的两个位置交换即可完成恢复
    12. if (ans[0] != null && ans[1] != null) {
    13. int temp = ans[0].val;
    14. ans[0].val = ans[1].val;
    15. ans[1].val = temp;
    16. }
    17. }
    18. // 通过非递归版本的中序遍历找到我们需要的两个降序位置
    19. public TreeNode[] in(TreeNode root) {
    20. // 当前遍历到的节点
    21. TreeNode cur = root;
    22. // 要返回的答案
    23. TreeNode[] ans = new TreeNode[2];
    24. // 标记当前是否找到了降序位置(用来标记是第一次找到降序位置还是第二次找到降序位置)
    25. boolean flag = false;
    26. // 记录中序序列的上一个节点,一开始设置为系统最小值
    27. TreeNode pre = new TreeNode(Integer.MIN_VALUE);
    28. // 用来实现非递归的中序遍历
    29. Stack stack = new Stack<>();
    30. while (cur != null || !stack.isEmpty()) {
    31. if (cur != null) {
    32. stack.push(cur);
    33. cur = cur.left;
    34. } else {
    35. cur = stack.pop();
    36. // 当前位置的值小于中序序列的上一个值,说明找到了降序
    37. // 如果flag==false,说明这个是找到的第一次降序
    38. if (cur.val < pre.val && !flag) {
    39. // 记录第一次降序的第一个位置
    40. ans[0] = pre;
    41. // 设置为true
    42. flag = true;
    43. }
    44. // 这里不能用else if,因为有可能出现的两次降序是连在一起的。或者如果只有一次降序的话,还需要记录这一次降序的第二个位置
    45. // flag == true表示这是第二次降序
    46. if (cur.val < pre.val && flag) {
    47. // 记录最后一次降序的第二个位置
    48. ans[1] = cur;
    49. }
    50. // 更新中序序列的上一个节点
    51. pre = cur;
    52. cur = cur.right;
    53. }
    54. }
    55. return ans;
    56. }
    57. }

    方法二(时间复杂度O(N),额外空间复杂度O(1))  

    1. class Solution {
    2. // 时间复杂度O(N),空间复杂度O(1)
    3. public void recoverTree(TreeNode root) {
    4. // 用来记录所需要的两个降序位置
    5. // ans[0]:表示第一次降序的第一个节点
    6. // ans[1]:表示最后一次降序的第二个节点(如果只有一次降序,那么这个就是唯一这一次降序的第二个节点)
    7. // 通过中序序列得到我们需要的降序位置
    8. TreeNode[] ans = in(root);
    9. TreeNode s1;
    10. TreeNode s2;
    11. // 将找到的两个位置交换即可完成恢复
    12. if (ans[0] != null && ans[1] != null) {
    13. int temp = ans[0].val;
    14. ans[0].val = ans[1].val;
    15. ans[1].val = temp;
    16. }
    17. }
    18. // 通过非递归版本的中序遍历找到我们需要的两个降序位置
    19. public TreeNode[] in(TreeNode root) {
    20. // 当前遍历到的节点
    21. TreeNode cur = root;
    22. // 要返回的答案
    23. TreeNode[] ans = new TreeNode[2];
    24. // 标记当前是否找到了降序位置(用来标记是第一次找到降序位置还是第二次找到降序位置)
    25. boolean flag = false;
    26. // 记录中序序列的上一个节点,一开始设置为系统最小值
    27. TreeNode pre = new TreeNode(Integer.MIN_VALUE);
    28. // 当前节点左树上的最右节点
    29. TreeNode leftTreeMostRightNode = null;
    30. // Morris遍历改中序遍历
    31. while (cur != null) {
    32. leftTreeMostRightNode = cur.left;
    33. if (leftTreeMostRightNode != null) {
    34. while (leftTreeMostRightNode.right != null && leftTreeMostRightNode.right != cur) {
    35. leftTreeMostRightNode = leftTreeMostRightNode.right;
    36. }
    37. if (leftTreeMostRightNode.right == null) {
    38. leftTreeMostRightNode.right = cur;
    39. cur = cur.left;
    40. continue;
    41. } else {
    42. leftTreeMostRightNode.right = null;
    43. }
    44. }
    45. // 当前位置的值小于中序序列的上一个值,说明找到了降序
    46. // 如果flag==false,说明这个是找到的第一次降序
    47. if (cur.val < pre.val && !flag) {
    48. // 记录第一次降序的第一个位置
    49. ans[0] = pre;
    50. // 设置为true
    51. flag = true;
    52. }
    53. // 这里不能用else if,因为有可能出现的两次降序是连在一起的。或者如果只有一次降序的话,还需要记录这一次降序的第二个位置
    54. // flag == true表示这是第二次降序
    55. if (cur.val < pre.val && flag) {
    56. // 记录最后一次降序的第二个位置
    57. ans[1] = cur;
    58. }
    59. // 更新中序序列的上一个节点
    60. pre = cur;
    61. cur = cur.right;
    62. }
    63. return ans;
    64. }
    65. }

    三、解题思路 

    这道题首先我们要知道搜索二叉树的一些性质,搜索二叉树的中序遍历应该是一个升序的序列。只要不是升序,就说明这个不是搜索二叉树。我们就基于这个性质,来求解这道题。

    可能有两对或一对降序:第一个错误节点是第一回降序的第一个节点, 第二个错误节点是最后一回降序的第二个节点(这个是通过观察法的得来的,自己列出几个例子,然后就可以发现这个规律了)。把错误节点交换值即可完成恢复。

    这道题可以直接在中序遍历的过程中找到降序位置,然后收集我们需要的两个数。这种写法的空间复杂度是O(树的高度)。这道题还可以在不影响时间复杂度的情况下将空间复杂度优化成O(1),可以采用Morris遍历就可以实现。

  • 相关阅读:
    0038__一文看懂NB-IOT/Lora/Zigbee三大技术的组网方式
    vscode插件路径转移C盘之外盘
    文件列表创建工具 Nifty File Lists mac中文版功能特色
    MySQL InnoDB 表不存在问题修复
    【ESP32】ESP32-Face人脸识别过程概述
    批量寄件教程
    【JS】常用表单验证(更新中...)
    安装 paddlepaddle paddleocr库,避坑指南
    AWS架构师认证有什么用?考试难吗?
    基于SSM框架的电影院售票网站
  • 原文地址:https://blog.csdn.net/cy973071263/article/details/127702764