1. 題幹描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
2. 回顧遍歷二叉樹的三種規則
- 前序遍歷:先自己,再左結點,最後右結點
- 中序遍歷:先左結點,再自己,最後右結點
- 後序遍歷:先左結點,再右結點,最後自己
由此可以獲得的規律和解題算法:
- 前序遍歷的第一個結點是root,對於中序遍歷的結果中應該是位於中間位置
- 獲得root後,從中序遍歷的結果來看,以root切分,可以得到兩棵子樹
- 之後繼續讀前序遍歷的結果,對應到中序遍歷的結果可以繼續分割,直到分割時只有一個葉子結點
- 以此類推,可以推斷出原始的二叉樹結構
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; }