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

166 篇文章 1 订阅
78 篇文章 46 订阅

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(树的高度)) 

class Solution {
    // 时间复杂度O(N),空间复杂度O(树的高度)
    public void recoverTree(TreeNode root) {
        // 用来记录所需要的两个降序位置
        // ans[0]:表示第一次降序的第一个节点
        // ans[1]:表示最后一次降序的第二个节点(如果只有一次降序,那么这个就是唯一这一次降序的第二个节点)
        // 通过中序序列得到我们需要的降序位置
        TreeNode[] ans = in(root);
        TreeNode s1;
        TreeNode s2;
        // 将找到的两个位置交换即可完成恢复
        if (ans[0] != null && ans[1] != null) {
            int temp = ans[0].val;
            ans[0].val = ans[1].val;
            ans[1].val = temp;
        }
    }

    // 通过非递归版本的中序遍历找到我们需要的两个降序位置
    public TreeNode[] in(TreeNode root) {
        // 当前遍历到的节点
        TreeNode cur = root;
        // 要返回的答案
        TreeNode[] ans = new TreeNode[2];
        // 标记当前是否找到了降序位置(用来标记是第一次找到降序位置还是第二次找到降序位置)
        boolean flag = false;
        // 记录中序序列的上一个节点,一开始设置为系统最小值
        TreeNode pre = new TreeNode(Integer.MIN_VALUE);
        // 用来实现非递归的中序遍历
        Stack<TreeNode> stack = new Stack<>();


        while (cur != null || !stack.isEmpty()) {
            if (cur != null) {
                stack.push(cur);
                cur = cur.left;
            } else {
                cur = stack.pop();

                // 当前位置的值小于中序序列的上一个值,说明找到了降序
                // 如果flag==false,说明这个是找到的第一次降序
                if (cur.val < pre.val && !flag) {
                    // 记录第一次降序的第一个位置
                    ans[0] = pre;
                    // 设置为true
                    flag = true;
                } 
                // 这里不能用else if,因为有可能出现的两次降序是连在一起的。或者如果只有一次降序的话,还需要记录这一次降序的第二个位置
                // flag == true表示这是第二次降序
                if (cur.val < pre.val && flag) {
                    // 记录最后一次降序的第二个位置
                    ans[1] = cur;
                }
                // 更新中序序列的上一个节点
                pre = cur;

                cur = cur.right;
            
            }
        }

        return ans;
    }
}

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

class Solution {
    // 时间复杂度O(N),空间复杂度O(1)
    public void recoverTree(TreeNode root) {
        // 用来记录所需要的两个降序位置
        // ans[0]:表示第一次降序的第一个节点
        // ans[1]:表示最后一次降序的第二个节点(如果只有一次降序,那么这个就是唯一这一次降序的第二个节点)
        // 通过中序序列得到我们需要的降序位置
        TreeNode[] ans = in(root);
        TreeNode s1;
        TreeNode s2;
        // 将找到的两个位置交换即可完成恢复
        if (ans[0] != null && ans[1] != null) {
            int temp = ans[0].val;
            ans[0].val = ans[1].val;
            ans[1].val = temp;
        }
    }

    // 通过非递归版本的中序遍历找到我们需要的两个降序位置
    public TreeNode[] in(TreeNode root) {
        // 当前遍历到的节点
        TreeNode cur = root;
        // 要返回的答案
        TreeNode[] ans = new TreeNode[2];
        // 标记当前是否找到了降序位置(用来标记是第一次找到降序位置还是第二次找到降序位置)
        boolean flag = false;
        // 记录中序序列的上一个节点,一开始设置为系统最小值
        TreeNode pre = new TreeNode(Integer.MIN_VALUE);

        // 当前节点左树上的最右节点
        TreeNode leftTreeMostRightNode = null;
        // Morris遍历改中序遍历
        while (cur != null) {
            leftTreeMostRightNode = cur.left;
            if (leftTreeMostRightNode != null) {
                while (leftTreeMostRightNode.right != null && leftTreeMostRightNode.right != cur) {
                    leftTreeMostRightNode = leftTreeMostRightNode.right;
                }

                if (leftTreeMostRightNode.right == null) {
                    leftTreeMostRightNode.right = cur;
                    cur = cur.left;
                    continue;
                } else {
                    leftTreeMostRightNode.right = null;
                }
            }

            // 当前位置的值小于中序序列的上一个值,说明找到了降序
            // 如果flag==false,说明这个是找到的第一次降序
            if (cur.val < pre.val && !flag) {
                // 记录第一次降序的第一个位置
                ans[0] = pre;
                // 设置为true
                flag = true;
            } 
            // 这里不能用else if,因为有可能出现的两次降序是连在一起的。或者如果只有一次降序的话,还需要记录这一次降序的第二个位置
            // flag == true表示这是第二次降序
            if (cur.val < pre.val && flag) {
                // 记录最后一次降序的第二个位置
                ans[1] = cur;
            }
            // 更新中序序列的上一个节点
            pre = cur;

            cur = cur.right;
        }

        return ans;
    }
}

三、解题思路 

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值