題目:105. 從前序與中序遍歷序列構造二叉樹
根據一棵樹的前序遍歷與中序遍歷構造二叉樹。
注意:
你可以假設樹中沒有重複的元素。
例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
3
/ \
9 20
/ \
15 7
第一種解法:遞迴解
首先
前序遍歷: 根 -> 左-> 右
中序遍歷:左 -> 根 -> 右
從前序遍歷我們可以知道第一個元素3
是根節點,再根據中序遍歷我們可以知道從第1
個元素到根節點3
之間的元素是全部都是根節點的左子樹,那麼我們就可以從中序遍歷來得知該根節點左子樹元素的個數,假設為x
,從而可以得出根節點左子樹元素在前序遍歷中的區間是多少,而在知道左子樹區間之後,根節點右子樹在前序遍歷中的區間我們也可以知道,參照下圖去理解
比如說下面這棵樹:
前序遍歷:3 9 6 8 20 15 7
中序遍歷:6 9 8 3 15 20 7
3
/ \
9 20
/ \ / \
6 8 15 7
根據程式碼進一步去理解
程式碼
class Solution {
// 遞迴解
int[] preorder;
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
int len = preorder.length;
this.preorder = preorder;
//將中序遍歷所有值放在雜湊表中,以減少每次在中序遍歷中尋找root值下標的時間,空間換時間
for (int i = 0; i < len; i++) {
map.put(inorder[i], i);
}
return buildTree(0, len - 1, 0, len - 1);
}
/**
* @param preLeft : 前序遍歷左邊界
* @param preRight : 前序遍歷右邊界
* @param inLeft : 中序遍歷左邊界
* @param inRight : 中序遍歷右邊界
* @return
*/
private TreeNode buildTree(int preLeft, int preRight, int inLeft, int inRight) {
// 遞迴終止條件,如果左邊界大於右邊界,遞迴終止,開始向上一層返回結果
if (preLeft > preRight || inLeft > inRight) {
return null;
}
// 獲取當前根節點的值
int temp = preorder[preLeft];
// 建立根節點
TreeNode root = new TreeNode(temp);
// 獲取根節點在中序遍歷中的下標值
int pIndex = map.get(temp);
// 遞迴獲取當前根節點的左子樹
// 其中 前序遍歷左子樹的右邊界 = pIndex - 1 - inLeft + preLeft + 1 = pIndex - inLeft + preLeft
root.left = buildTree(preLeft + 1, pIndex - inLeft + preLeft, inLeft, pIndex - 1);
// 遞迴獲取當前根節點的右子樹
root.right = buildTree(pIndex - inLeft + preLeft + 1, preRight, pIndex + 1, inRight);
return root;
}
}
第二種解法:迭代
迭代是一種很巧妙地的解法
繼續看下面這顆樹
前序遍歷:3 9 6 8 20 15 7
中序遍歷:6 9 8 3 15 20 7
3
/ \
9 20
/ \ / \
6 8 15 7
首先,我們先只看前序遍歷
- 遇到第一個元素
3
,那麼3
肯定是作為根節點 - 遇到第二個元素
9
,那麼9
可能是左子樹也可能是右子樹,此時我們結合中序遍歷來看,中序遍歷的第一個元素是6
,那麼我們就可以確定9
是左子樹,因為假如9
是右子樹,那麼中序遍歷的第一個元素應該是根節點3
,但此時很明顯不是3
,所以可以確定9
是左子樹 - 再繼續往前走,遇到了元素
6
,同理,6
是元素9
的左子樹,但此時我們發現6
與中序遍歷第一個元素相等了,這說明左子樹已經遍歷到了末尾,下一個元素只能是右子樹,但究竟是元素6
的右子樹?還是元素9
的右子樹?又或者是元素3
的右子樹?好,我們接著 往下看 - 現在是遇到了元素
8
,我們現在有三種情況- 第一種情況:元素
8
是元素6
的右子樹,那麼此時中序遍歷的結果就應該是6、8、9、3...
- 第二種情況:元素
8
是元素9
的右子樹,此時中序遍歷的結果應該是6、9、8、3...
- 第三種情況:元素
8
是元素3
的右子樹,此時中序遍歷的結果是6、9、3、8...
- 第一種情況:元素
- 我們知道,第二種情況是與我們的中序遍歷結果相符合的,所以當我們倒序遍歷已經遇到過的元素時,當前遍歷的元素
8
倒序遍歷中最後一個相等的元素9
的右子樹,而符合可以倒序遍歷已經遍歷過元素的資料結構就是棧,我們可以用棧來儲存已經遍歷過的元素。 - 以此類推,我們可以構造完整棵樹
程式碼
class Solution {
//迭代,棧,從後往前遍歷解
public TreeNode buildTree2(int[] preorder, int[] inorder) {
Deque<TreeNode> stack = new ArrayDeque<>();
int pre = 0;
int in = 0;
//構造當前正在遍歷的節點
TreeNode curRoot = new TreeNode(preorder[pre]);
TreeNode root = curRoot;
stack.push(curRoot);
pre++;
while (pre < preorder.length) {
if (curRoot.val == inorder[in]) {
// 如果當前遍歷節點值與中序遍歷值相等,不斷將棧中元素頂出棧,直到值不相等
while (!stack.isEmpty() && stack.peek().val == inorder[in]) {
curRoot = stack.peek();
stack.pop();
in++;
}
// 當前遍歷的節點就是最後一個值相等的節點的右子樹
curRoot.right = new TreeNode(preorder[pre]);
curRoot = curRoot.right;
stack.push(curRoot);
pre++;
} else {
//如果值不相等就說明是左子樹
curRoot.left = new TreeNode(preorder[pre]);
curRoot = curRoot.left;
stack.push(curRoot);
pre++;
}
}
return root;
}
}
拓展
趁著手熱,可以去做一下這道題:106. 從中序與後序遍歷序列構造二叉樹