【挑戰劍指offer】系列04:重建二叉樹 原

1. 題幹描述

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。

2. 回顧遍歷二叉樹的三種規則

  • 前序遍歷:先自己,再左結點,最後右結點
  • 中序遍歷:先左結點,再自己,最後右結點
  • 後序遍歷:先左結點,再右結點,最後自己

由此可以獲得的規律和解題算法:

  1. 前序遍歷的第一個結點是root,對於中序遍歷的結果中應該是位於中間位置
  2. 獲得root後,從中序遍歷的結果來看,以root切分,可以得到兩棵子樹
  3. 之後繼續讀前序遍歷的結果,對應到中序遍歷的結果可以繼續分割,直到分割時只有一個葉子結點
  4. 以此類推,可以推斷出原始的二叉樹結構

3. 遞歸重建二叉樹

private static int preValueIndex = 0;
public static TreeNode reConstructBinaryTree(int[] pre, int[] in) {
    //如果子樹只有一個結點,證明是葉子
    if (in.length == 1) {
        return new TreeNode(pre[preValueIndex]);
    }

    //既然已經過了上面的判斷,證明不是葉子節點,可以建立父結點了
    TreeNode thisNode = new TreeNode(pre[preValueIndex]);
    //確定當前遍歷的結點在中序遍歷的位置
    int inRootValueIndex = ArrayUtils.indexOf(in, pre[preValueIndex]);
    //重建左子樹
    TreeNode leftChildren = null;
    if (0 < inRootValueIndex) {
        //移動前序遍歷的遊標
        preValueIndex++;
        int[] leftSubTree = ArrayUtils.subarray(in, 0, inRootValueIndex);
        leftChildren = reConstructBinaryTree(pre, leftSubTree);
    }
    //重建右子樹
    TreeNode rightChildren = null;
    if ((inRootValueIndex + 1) < in.length) {
        //移動前序遍歷的遊標
        preValueIndex++;
        int[] rightSubTree = ArrayUtils.subarray(in, inRootValueIndex + 1, in.length);
        rightChildren = reConstructBinaryTree(pre, rightSubTree);
    }
    
    //包裝兩個子樹的父結點(即本身)
    thisNode.left = leftChildren;
    thisNode.right = rightChildren;
    return thisNode;
}

4. 變式:不允許使用遞歸

public static TreeNode reConstructBinaryTreeWithoutRecurrence(int[] pre, int[] in) {
    Stack<Entry<int[], TreeNode>> stack = new Stack<>();
    int preValueIndex = 0;
    
    //輔助值,用於標記下一個結點是否應該放到右孩子上
    boolean isRightSubTree = false;
    TreeNode root = null;
    int[] arrPointer = in;
    while (!stack.isEmpty() || (arrPointer != null && arrPointer.length > 0)) {
        while (arrPointer != null && arrPointer.length > 0) {
            TreeNode node = new TreeNode(pre[preValueIndex++]);
            int thisNodeValueIndex = ArrayUtils.indexOf(arrPointer, node.val);
            int[] subarray = ArrayUtils.subarray(arrPointer, 0, thisNodeValueIndex);
            //如果棧不爲空,證明該結點有父結點
            if (!stack.isEmpty()) {
                TreeNode parent = stack.peek().getValue();
                //如果右孩子標記爲true,則當前結點是從下面的if結構中首次過來的
                if (isRightSubTree) {
                    parent.right = node;
                    isRightSubTree = false;
                }else {
                    parent.left = node;
                }
            }
            //進棧(整體數組和父結點的值)
            stack.push(new Entry<>(arrPointer, node));
            arrPointer = subarray;
            //如果subarray爲空數組,證明沒有左子樹了,跳出while循環
            if (subarray.length == 0) {
                break;
            }
        }
        if (!stack.isEmpty()) {
            Entry<int[],TreeNode> childrenEntry = stack.peek();
            //如果數組長度爲1,則該結點爲葉子結點,直接彈棧
            //或者:如果此時isRightSubTree也是true,證明本來要去找右子樹的,結果沒找到,這個值沒有被修改
            //這就代表:當前結點已經構建完畢
            boolean childrenIsLeaf = childrenEntry.getKey().length == 1;
            if (childrenIsLeaf || isRightSubTree) {
                stack.pop();
                //如果此時二叉樹已經遍歷完畢,並且棧內只剩下一個元素,直接返回
                if (preValueIndex == in.length && stack.size() == 1) {
                    return stack.pop().getValue();
                }
            }
            Entry<int[],TreeNode> parentEntry = stack.peek();
            //如果當前的孩子結點是父結點的左孩子,則用父結點的value取索引;右孩子則用孩子結點value取索引
            boolean childrenIsRight = parentEntry.getValue().right == childrenEntry.getValue();
            //這個地方到底使用父結點的value還是子結點的value,要取決於當前子結點是否爲右孩子
            int thisNodeValueIndex = ArrayUtils.indexOf(parentEntry.getKey(), 
                    (childrenIsRight ? childrenEntry : parentEntry).getValue().val);
            int[] rightSubTree = ArrayUtils.subarray(parentEntry.getKey(), 
                    thisNodeValueIndex + 1, parentEntry.getKey().length);
            arrPointer = rightSubTree;
            isRightSubTree = true;
        }
    }
    
    return root;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章