前言
本系列的文章爲筆者學習《劍指offer第二版》後的筆記整理以及Java實現,解法不保證與書上一致。
另外,我最近在系統整理一些 Java 後臺方面的面試題和參考答案,有找工作需求的童鞋,歡迎關注我的 Github 倉庫,如果覺得不錯可以點個 star 關注 :
題目描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建如下圖所示的二叉樹並輸出它的頭節點。二叉樹節點的定義如下:
private class BinaryTreeNode {
int val;
BinaryTreeNode left;
BinaryTreeNode right;
BinaryTreeNode(int x) {
val = x;
}
}
解題思路
在二叉樹中,前序遍歷的第一個節點即爲樹的根節點。根據二叉樹的前序遍歷序列確定了根節點後,遍歷中序遍歷序列可以找到根節點的位置,然後根據二叉樹中序遍歷的特點可知,根節點左邊的節點即爲左子樹的所有節點,而根節點右邊的節點即爲右子樹的所有節點。
根據上述遍歷我們可以分別找到了左右子樹的前序遍歷序列和中序遍歷序列,很顯然,我們也可以用同樣的方法分別構建左、右子樹。也就是說,接下來完全可以不斷地遞歸去實現。
比如輸入前序遍歷序列 {1,2,4,7,3,5,6,8} 和中序遍歷序列 {4,7,2,1,5,3,8,6}。由前序序列可知根結點是 1,於是從中序序列可知,{4, 7, 2} 屬於根結點 1 的左子樹,有 3 個結點;{5, 3 ,8, 6} 屬於根結點 1 的右子樹。回到前序序列,除開第一個根結點,其後 3 個結點 {2, 4, 7} 就是左子樹,餘下的 {3, 5, 6 ,8} 是右子樹。按照這些關係可以精確地將數組分解成兩個子數組,遞歸地對兩個數組進行同樣的操作即可。
package com.offers.chapter2.question07;
/**
* 輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹並返回它的頭節點。
* 假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。
*
* @author Rotor
* @since 2019/9/30 16:39
*/
public class ReConstructBinaryTree {
/**
* 二叉樹節點類
*/
private static class BinaryTreeNode {
int val;
BinaryTreeNode left;
BinaryTreeNode right;
BinaryTreeNode(int x) {
val = x;
}
}
/**
* @param preorder 前序遍歷序列
* @param inorder 中序遍歷序列
* @return 樹的根節點
*/
public static BinaryTreeNode reConstructBinaryTree(int[] preorder, int[] inorder) {
// 合法性判斷,前序遍歷和後序遍歷連個數組不能爲空並且節點的數目應該相同
if (preorder == null || inorder == null || preorder.length != inorder.length || inorder.length < 1) {
return null;
}
return contructTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
/**
* @param preorder 前序遍歷序列
* @param startPreorder 前序遍歷的起始位置(左指針)
* @param endPreorder 前序遍歷序列的結束位置(右指針)
* @param inorder 中序遍歷序列
* @param startInorder 中序遍歷的起始位置(左指針)
* @param endInorder 中序遍歷的結束位置(右指針)
* @return 樹的根節點
*/
private static BinaryTreeNode contructTree(int[] preorder, int startPreorder, int endPreorder,
int[] inorder, int startInorder, int endInorder) {
// 前序序列或中序序列起始位置大於結束位置,說明不能再繼續遞歸分解成子數組了,返回空子樹給父節點
if (startPreorder > endPreorder || startInorder > endInorder) {
return null;
}
// 前序序列的第一個節點的值就是根節點的值
int rootvalue = preorder[startPreorder];
// 創建根節點
BinaryTreeNode root = new BinaryTreeNode(rootvalue);
// 遍歷中序遍歷序列,找到中序序列中根節點的位置
int rootIndex = startInorder;
while (rootIndex <= endInorder && inorder[rootIndex] != rootvalue) {
rootIndex++;
}
// 如果遍歷整個中序遍歷序列都沒有找到根節點,說明輸入了無效參數
if (rootIndex > endInorder) {
throw new RuntimeException("Invalid input");
}
// 左子樹的長度
int leftLength = rootIndex - startInorder;
int leftPreorderEnd = startPreorder + leftLength;
/**
* 構建左子樹
* 左子樹對應的前序遍歷序列中的位置爲:[startPreorder + 1, startPreorder + rootIndex - startInorder]
* 左子樹對應的中序遍歷序列中的位置爲:[startInorder, rootIndex - 1]
*/
root.left = contructTree(preorder, startPreorder + 1, leftPreorderEnd, inorder, startInorder, rootIndex - 1);
/**
* 構建右子樹
* 右子樹對應的前序遍歷序列中的位置爲:[startPreorder + rootIndex - startInorder + 1, endPreorder]
* 右子樹對應的中序遍歷序列中的位置爲:[rootIndex + 1, endInorder]
*/
root.right = contructTree(preorder, leftPreorderEnd + 1, endPreorder, inorder, rootIndex + 1, endInorder);
// 返回樹的根節點
return root;
}
// 中序遍歷二叉樹
public static void printTree(BinaryTreeNode root) {
if (root != null) {
printTree(root.left);
System.out.print(root.val + " ");
printTree(root.right);
}
}
// 普通二叉樹
// 1
// / \
// 2 3
// / / \
// 4 5 6
// \ /
// 7 8
private static void test1() {
int[] preorder = {1, 2, 4, 7, 3, 5, 6, 8};
int[] inorder = {4, 7, 2, 1, 5, 3, 8, 6};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
// 所有結點都沒有右子結點
// 1
// /
// 2
// /
// 3
// /
// 4
// /
// 5
private static void test2() {
int[] preorder = {1, 2, 3, 4, 5};
int[] inorder = {5, 4, 3, 2, 1};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
// 所有結點都沒有左子結點
// 1
// \
// 2
// \
// 3
// \
// 4
// \
// 5
private static void test3() {
int[] preorder = {1, 2, 3, 4, 5};
int[] inorder = {1, 2, 3, 4, 5};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
// 樹中只有一個結點
private static void test4() {
int[] preorder = {1};
int[] inorder = {1};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
// 完全二叉樹
// 1
// / \
// 2 3
// / \ / \
// 4 5 6 7
private static void test5() {
int[] preorder = {1, 2, 4, 5, 3, 6, 7};
int[] inorder = {4, 2, 5, 1, 6, 3, 7};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
// 輸入空指針
private static void test6() {
reConstructBinaryTree(null, null);
}
// 輸入的兩個序列不匹配
private static void test7() {
int[] preorder = {1, 2, 4, 5, 3, 6, 7};
int[] inorder = {4, 2, 8, 1, 6, 3, 7};
BinaryTreeNode root = reConstructBinaryTree(preorder, inorder);
printTree(root);
}
public static void main(String[] args) {
test1();
System.out.println();
test2();
System.out.println();
test3();
System.out.println();
test4();
System.out.println();
test5();
System.out.println();
test6();
System.out.println();
test7();
}
}
【注】代碼測試用例參開來源:https://github.com/Wang-Jun-Chao/coding-interviews/blob/master/src/Test06.java
總結
- 在構建二叉樹時,我們可以將構建二叉樹地大問題分解成構建左、右子樹地兩個小問題;
- 很顯然,可以發現小問題和大問題在本質上是一樣地,因此可以用遞歸的方式解決。
後記
如果你同我一樣想要努力學好數據結構與算法、想要刷 LeetCode 和劍指 offer,歡迎關注我 GitHub 上的 LeetCode 題解:awesome-java-notes