【LeetCode-114】二叉树展开为链表 :Morris&特殊的先序遍历 难度:中等

236 阅读2分钟

原题

  给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。

解法

因在下水平有限,探索出两种方法!

方式一:使用类似Morris方法,找最右节点

展开后的顺序是先序遍历。使用类似Morris方法,找左子树的最右节点,然后将左子树直接接在最右节点后,然后考虑下一个结点,依次以上操作!

  1. 将左子树插入到右子树的地方
  2. 将原来的右子树接到左子树的最右边节点
  3. 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null 如图理解:
    在这里插入图片描述
    在这里插入图片描述
    代码如下:
 public void flatten(TreeNode root) {
        //判空
        if(root==null){
            return;
        }
        while (root!=null){
            //如果左子树为空,直接考虑下一个节点,即直接转到右子树
            if(root.left==null) {
                root = root.right;
            }else{
                TreeNode pre = root.left;//找左子树的最右结点
                while (pre.right!=null){
                    pre = pre.right;
                }
                //将原来的右子树接到左子树的最右边节点
                pre.right = root.right;
                //将左子树接到原来右子树位置
                root.right = root.left;
                //左子树置空
                root.left = null;
                //继续考虑下一个节点
                root = root.right;
            }
        }
    }

方式二:特殊的先序遍历

普通的先序遍历(迭代)

public void preorder(TreeNode root){
        Deque<TreeNode> stk = new LinkedList<>();
        if (root==null){
            return;
        }
        while (root!=null || !stk.isEmpty()){
            while (root!=null){
                System.out.println(root.val);//根结点
                stk.push(root.left);//左节点入栈
            }
            root = stk.pop();
            root = root.right;//访问右节点
        }
    } 

特殊的先序遍历(提前将右孩子保存到栈中,我们利用这种遍历方式就可以防止右孩子的丢失了)

public void preorder2(TreeNode root){
        Deque<TreeNode> stk = new LinkedList<>();
        if (root==null){
            return;
        }
        stk.push(root);//根结点入栈
        while (!stk.isEmpty()){
            TreeNode temp = stk.pop();
            System.out.println(temp.val);//访问根结点
            if (temp.right!=null){//右节点不为空就先入栈,因为栈是先进后出,而先序遍历的右节点最后遍历
                stk.push(temp.right);
            }
            if (temp.left!=null){
                stk.push(temp.left);
            }
        }
    }

到这,我们应该有了思路了,

  • 题目其实就是将二叉树通过右指针,组成一个链表
  • 就是说,我们用temp代表当前访问的结点
  • 只需要将当前结点的左节点接到当前结点的右节点位置,就可以完成整体往右子树拉伸为链表操作!
  • 所以我们可以利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。
  • 因为我们用栈保存了右孩子,所以不需要担心右孩子丢失了。用一个 pre 变量保存上次遍历的节点

代码示例:

 public void flatten2(TreeNode root) {
        Deque<TreeNode> stk = new LinkedList<>();
        if (root==null){
            return;
        }
        stk.push(root);//根结点入栈
        TreeNode pre =null;
        while (!stk.isEmpty()){
            TreeNode temp = stk.pop();
//            System.out.println(temp.val);//访问根结点
            if(pre!=null){//不是第一次遍历,存在上一结点
                pre.right = temp;//当前节点其实是上一节点的左节点或者右节点,让上一节点右节点指向当前节点
                pre.left = null;//上一节点的左结点置空
            }

            if (temp.right!=null){//右节点不为空就先入栈,因为栈是先进后出,而先序遍历的右节点最后遍历
                stk.push(temp.right);
            }
            if (temp.left!=null){//左节点入栈
                stk.push(temp.left);
            }
            pre = temp;//用pre保存上一次遍历的结点,以备安排其左右结点
        }
    }

总体来说,第一种容易理解,第二种难以理解,但其空间复杂度有所降低!