恢复二叉搜索树多种解法

414 阅读3分钟

1  题目介绍

1.1  题目介绍

给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
**进阶:**使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?

实例一

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

题目来源:leetcode-cn.com/problems/re…

1.2  题目分析

在分析之前,先介绍一个概念:前驱节点,二叉搜索树的前驱节点指中序遍历的前一个节点;

二叉搜索树中序遍历的结果是一个递增的数组;正常的二叉搜索树中序遍历结果如下图

交换中序遍历前驱节点中序遍历如下图所示

交换其他节点中序遍历如下图所示

以上分析得出以下结果

  • 第1个错误节点:第一个逆序对中的较大节点
  • 第2个错误节点:最后一个逆序对中的较小节点;
  • 交换两个错误节点的值即可恢复二叉搜索树;

2  中序遍历寻找逆序对实现

采用递归方法进行中序遍历寻找逆序对;

var recoverTree = function(root) {
    let pre = null;
    let first = null;
    let second = null;
    findWrongNodes(root);
    // 交换两个错误节点值
    let tmp = first.val;
    first.val =  second.val;
    second.val = tmp;

    function findWrongNodes(root) {
        // 中序遍历寻找逆序对
        if(root == null) return;
        findWrongNodes(root.left);
        find(root);
        findWrongNodes(root.right);
    }

    function find(node) {
        // 出现了逆序对
        if(pre !== null && pre.val > node.val) {
            // 第二个错误的节点,最后一个逆序对中较小的那个节点
            second = node;
            // 第一个错误节点,第一个逆序对中较大的那个节点
            if(first != null) return;
            first = pre;
        }
        pre = node;
    }
};

时间复杂度:O(n);
空间复杂度:O(n);

3  Morris遍历寻找逆序对实现

使用Morris方法遍历二叉树,可以实现时间复杂度O(n),空间复杂度O(1);

3.1  Morris遍历介绍

        Morris遍历是一种常数空间的遍历方法,其本质是线索二叉树(Threaded Binary Tree);Morris遍历是利用树的叶子节点左右孩子为空(存在大量空闲指针),实现缩减空间开销;Morris中序遍历是指在遍历过程中,通过利用叶子节点空的right指针,指向中序遍历的后继节点,从而避免了对栈的依赖;实现空间复杂的降为O(1); 下面介绍二叉搜索树中序遍历的Morris遍历;

3.2  分析

执行步骤

假设遍历到当前节点是N

  1. 如果N.left != null,找到N的前驱节点P;
    a,如果P.right == null; P.right = N, N = N.left; 回到步骤1
    b,如果P.right == N; P.right = null, 此时N为中序遍历的节点,N = N.right, 回到步骤1;
  2. 如果N.left == null;即找到中序遍历的节点,N = N.right,回到步骤1
  3. 重复步骤1,2直到N == null;

3.3  代码实现

var recoverTree = function(root) {
    let pre = null;
    let first = null;
    let second = null;
    
    while(root != null) {
        if(root.left != null) {
            let pred = root.left;
            while(pred.right !== null && pred.right != root) {
                pred = pred.right;
            }
            if(pred.right == null) {
                pred.right = root;
                root = root.left;
            } else {
                find(root);
                pred.right = null;
                root = root.right;
            }
        } else {
            find(root);
            root = root.right;
        }
    }

    let tmp = first.val;
    first.val =  second.val;
    second.val = tmp;

    function find(node) {
        // 出现了逆序对
        if(pre !== null && pre.val > node.val) {
            // 第二个错误的节点,最后一个逆序对中较小的那个节点
            second = node;
            // 第一个错误节点,第一个逆序对中较大的那个节点
            if(first != null) return;
            first = pre;
        }
        pre = node;
    }
};

时间复杂度:O(n);
空间复杂度:O(1);