題目#105.從前序與中序遍歷序列構(gòu)造二叉樹
根據(jù)一棵樹的前序遍歷與中序遍歷構(gòu)造二叉樹。
注意:
你可以假設(shè)樹中沒有重復(fù)的元素。
例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
3
/ \
9 20
/ \
15 7
題目分析
題中提到,給出兩個數(shù)組分別保存二叉樹的前序遍歷和中序遍歷,如何從前序遍歷和中序遍歷恢復(fù)二叉樹呢?
我們可以拿題中給出的二叉樹分析分析:
對于下面兩個數(shù)組以及二叉樹的前中序遍歷的定義:
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
3
/ \
9 20
/ \
15 7
我們可以分別將前中序遍歷的兩個數(shù)組分為三個部分,分別為根節(jié)點與左右子樹:
前序遍歷 preorder = [3] [9] [20,15,7]
中序遍歷 inorder = [9] [3] [15,20,7]
下面說下為什么這么分:
-
前序遍歷的根節(jié)點必然是第一個,所以很容易將根節(jié)點找到
得到根節(jié)點在中序遍歷中的索引index
-
中序遍歷中,根節(jié)點在左右子樹中間,所以根據(jù)中序遍歷,我們能得到左右子節(jié)點的長度
[9,3,15,20,7] -> [9|3|15,20,7]
那么左子樹的長度為index - 0
,右子樹的長度為inorder.lastIndex - index
構(gòu)建二叉樹
上面的分析幫我們將數(shù)組分為了左右子樹和根節(jié)點,我們得到了左右子樹和新的左右子樹區(qū)間,遞歸就呼之欲出了。
遞歸已經(jīng)使我們的老熟人了,同樣還是考慮兩點:
-
遞歸結(jié)束條件
顯然,數(shù)組中沒有元素的時候,需要我們返回的二叉樹就是null,那么我們可以將數(shù)組的是否有元素作為遞歸結(jié)束條件。為了避免數(shù)組的頻繁賦值導(dǎo)致程序效率低下,我們使用數(shù)組和左右index的形式給出一個數(shù)組范圍如a,b
分別代表前序數(shù)組的左右范圍,c,d
分別代表中序數(shù)組的左右范圍,于是,遞歸結(jié)束條件可以寫為:if (a > b) return null
-
遞歸體
既然要構(gòu)造TreeNode
,那就得給root賦值左右節(jié)點和根節(jié)點-
根節(jié)點
val root = TreeNode(preorder[a])
-
左右子節(jié)點
root.left = buildTree(preorder, aLeft, bLeft, inorder, cLeft, dLeft)
root.right = buildTree(preorder, aRight, bRight, inorder, cRight, dRight)
aLeft,bLeft等這些值按照上面的分析,可以很簡單求得。
-
根節(jié)點
最終代碼
class Solution {
fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {
return buildTree(preorder, 0, preorder.lastIndex, inorder, 0, inorder.lastIndex)
}
private fun buildTree(preorder: IntArray, a: Int, b: Int, inorder: IntArray, c: Int, d: Int): TreeNode? {
if (a > b) return null
val root = TreeNode(preorder[a])
val index = inorder.indexOf(preorder[a])
val countLeft = index - c
val countRight = d - index
root.left = buildTree(preorder, a + 1, a + countLeft, inorder, c, c + countLeft - 1)
root.right = buildTree(preorder, b - countRight + 1, b, inorder, d - countRight + 1, d)
return root
}
}