劍指Offer_#7_重建二叉樹
阿新 • • 發佈:2020-07-31
劍指Offer_#7_重建二叉樹
Contents
題目
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。
例如,給出
前序遍歷 preorder =[3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
3
/ \
9 20
/ \
15 7
限制:
0 <= 節點個數 <= 5000
思路分析
整體思路
引用題解區liweiwei大佬的一個圖解 題解連結
這個圖片很清晰地展示了本題的思路。
整體的思路如下:
- 找到當前樹的根節點。根節點一定是前序遍歷序列的第一個,即上圖中的1。
- 找到根節點在中序遍歷序列當中的位置,即上圖中pivot指向的位置。
- 根據中序遍歷中根節點的位置,可以將整個
inorder
陣列劃分為左子樹部分和右子樹部分,即綠色框和紅色框部分。 - 根據中序遍歷中左子樹部分的個數,反過來又可以把
preorder
陣列劃分為左子樹部分和右子樹部分,即綠色框和紅色框部分。
以上的步驟將前序遍歷序列和中序遍歷序列劃分成為3部分
- 根節點
- 左子樹
- 右子樹
但是這還不足以重構整個二叉樹,因為這是個遞迴問題,還需要解決遞迴子問題
- 左子樹部分還可以劃分為左子樹的左子樹,左子樹的右子樹
- 右子樹部分還可以劃分為右子樹的左子樹,右子樹的右子樹
我們需要編寫遞迴函式來實現上述邏輯。
遞迴函式
遞迴引數(函式簽名)
TreeNode recur(int preL,int preR,int inL,int inR)
preL
,preR
表示當前子樹在前序遍歷序列preorder當中的左右邊界inL
,inR
表示當前子樹在中序遍歷序列inorder當中的左右邊界
遞迴終止條件
如果左邊界大於右邊界,表示當前的子樹沒有任何節點,是null
if(preL > preR || inL > inR) return null;
遞推過程
- 構建當前子樹的根節點
root
,root
的值是preorder[preL]
- 在中序遍歷序列
inorder
root
的值所在的位置- 方法1:提前構建一個
HashMap
,儲存鍵值對<inorder[i],i>
,可以直接查詢到 - 方法2:遍歷
inorder
陣列,找到root
的值
- 方法1:提前構建一個
- 根據上面的圖解,我們可以劃分出
root
的左右子樹在兩個序列裡的範圍。得到root
的左右子樹的preL
,preR
,inL
,inR
。呼叫遞迴子函式,構造出root
的左右子樹。
返回值(回溯)
返回當前構建的root
子樹,成為更高一層遞迴函式中的左右子樹。
其他細節
特殊輸入
preorder
/inorder
是nullpreorder
/inorder
長度為0preorder
和inorder
長度不同
以上情況都無法重建出一個二叉樹,返回null
全域性變數
hashMap
變數,用於儲存鍵值對<inorder[i],i>
po
變數,儲存preorder
陣列,避免遞迴函式引數太多
解答
class Solution {
HashMap<Integer,Integer> map = new HashMap();
//前序遍歷序列的全域性變數,避免遞迴函式的引數太多
int[] po;
public TreeNode buildTree(int[] preorder, int[] inorder) {
int preLen = preorder.length;
int inLen = inorder.length;
//特殊輸入:null,空陣列,兩陣列長度不同
if(preorder == null || inorder == null || preLen == 0 || inLen == 0 || preLen != inLen)
return null;
po = preorder;
//將中序遍歷序列的<inorder[i],i>存入map,以便在inorder陣列中快速找到子樹根節點的值
for(int i = 0;i <= inorder.length - 1;i++){
map.put(inorder[i],i);
}
//開啟遞迴呼叫
return recur(0,preLen - 1,0,inLen - 1);
}
//遞迴函式引數:
//preL,preR表示當前子樹在前序遍歷序列preorder當中的左右邊界
//inL,inR表示當前子樹在中序遍歷序列inorder當中的左右邊界
private TreeNode recur(int preL,int preR,int inL,int inR){
//遞迴出口條件:左邊界大於右邊界,含義是當前子樹為空
if(preL > preR || inL > inR) return null;
//當前子樹的根節點的值就是po[preL]
int pivot = po[preL];
TreeNode root = new TreeNode(pivot);
//利用map,找到當前子樹根節點在中序遍歷序列inorder中的索引
int pivotIndex = map.get(pivot);
//開啟下一級遞迴呼叫,將程式阻塞在這裡,直到滿足遞迴終止條件
//重點在於遞推過程中的4個引數,必須在紙上先畫出圖,推匯出引數,再寫程式碼
root.left = recur(preL + 1,preL + pivotIndex - inL,inL,pivotIndex - 1);
root.right = recur(preL + pivotIndex - inL + 1,preR,pivotIndex + 1,inR);
//回溯,將構造好的子樹返回給上一級遞迴函式
return root;
}
}
複雜度分析
時間複雜度:O(n)
- 初始化
hashmap
,複雜度是O(n)
- 每個遞迴函式構建一個節點,所以遞迴函式呼叫
O(n)
次
空間複雜度:O(n)
hashmap
佔用空間O(n)
- 遞迴呼叫佔用空間
O(n)