【每天一題】劍指 Offer 07. 重建二叉樹

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。
注意:
你可以假設樹中沒有重複的元素。

例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:[3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

題目分析:

前序遍歷特點: 節點按照 [ 根節點 | 左子樹 | 右子樹 ] 排序,以題目示例爲例:[ 3 | 9 | 20| 15 |7 ]
中序遍歷特點: 節點按照 [ 左子樹 | 根節點 | 右子樹 ] 排序,以題目示例爲例:[ 9 | 3 | 15 |20 |7 ]
根據題目描述輸入的前序遍歷和中序遍歷的結果中都不含重複的數字,其表明樹中每個節點值都是唯一的。

根據以上特點,可以按順序完成以下工作:

前序遍歷的首個元素即爲根節點 root 的值;
在中序遍歷中搜索根節點 root 的索引 ,可將中序遍歷劃分爲 [ 左子樹 | 根節點 | 右子樹 ] 。
根據中序遍歷中的左(右)子樹的節點數量,可將前序遍歷劃分爲 [ 根節點 | 左子樹 | 右子樹 ] 。
自此可確定 三個節點的關係 :

  1. 樹的根節點、
  2. 左子樹根節點
  3. 右子樹根節點(即前序遍歷中左(右)子樹的首個元素)。

子樹特點: 子樹的前序和中序遍歷仍符合以上特點,以題目示例的右子樹爲例:前序遍歷:[20 | 15 | 7],中序遍歷 [ 15 | 20 | 7 ] 。

根據子樹特點,我們可以通過同樣的方法對左(右)子樹進行劃分,每輪可確認三個節點的關係 。此遞推性質讓我們聯想到用 遞歸方法 處理。

在這裏插入圖片描述
僞代碼

遞推參數: 前序遍歷的左邊界preleft、前序遍歷的右邊界preRight、中序遍歷左邊界inleft、中序遍歷右邊界inRight。
終止條件: 當preleft>preRight || inleft>inRight ,子樹中序遍歷爲空,說明已經越過葉子節點,此時返回 null 。
遞推工作:
	建立根節點root: 值爲前序遍歷中索引爲pre_root的節點值。
	搜索根節點root在中序遍歷的索引i: 爲了提升搜索效率,本題解使用哈希表 dic 預存儲中序遍歷的值與索引的映射關係,每次搜索的時間複雜度爲 O(1)。
	構建根節點root的左子樹和右子樹: 通過調用 myBuildTree() 方法開啓下一層遞歸。
		左子樹: 先序遍歷中「從 左邊界preleft+1 開始的 pIndex-inleft+preleft」右邊界的元素就對應了中序遍歷中「從 左邊界inleft 開始到 根節點定位pIndex-1」的元素
		右子樹: 先序遍歷中「從 左邊界+1+左子樹節點數目 開始到 右邊界」的元素就對應了中序遍歷中「從 根節點定位+1 到 右邊界」的元素
返回值: 返回 root,含義是當前遞歸層級建立的根節點 root 爲上一遞歸層級的根節點的左或右子節點。
class Solution {
    public TreeNode myBuildTree(int[] preorder, int preleft, int preRight,
                                int[] inorder, int inleft, int inRight,
                                Map<Integer, Integer> map) {
        if(preleft>preRight || inleft>inRight){
            return null;
        }
         // 前序遍歷中的第一個節點就是根節點
        int rootVal=preorder[preleft];
        // 先把根節點建立出來
        TreeNode root = new TreeNode(rootVal);
		 // 在中序遍歷中定位根節點
        int pIndex=map.get(rootVal);
		// 遞歸地構造左子樹,並連接到根節點
        // 先序遍歷中「從 左邊界preleft+1 開始的 pIndex-inleft+preleft」右邊界的元素就對應了中序遍歷中「從 左邊界inleft 開始到 根節點定位pIndex-1」的元素
        root.left=myBuildTree(preorder,preleft+1,pIndex-inleft+preleft,
                inorder,inleft,pIndex-1,map);
        // 遞歸地構造右子樹,並連接到根節點
        // 先序遍歷中「從 左邊界+1+左子樹節點數目 開始到 右邊界」的元素就對應了中序遍歷中「從 根節點定位+1 到 右邊界」的元素
        root.right=myBuildTree(preorder,pIndex-inleft+preleft+1,preRight,
                inorder,pIndex+1,inRight,map);

        return root;
    }

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int preLen=preorder.length;
        int inLen=inorder.length;
        Map<Integer,Integer> map=new HashMap<>(preLen);
        // 構造哈希映射,幫助我們快速定位根節點
        for(int i=0;i<inLen;i++){
            map.put(inorder[i],i);
        }
        return myBuildTree(preorder,0,preLen-1,inorder,0,inLen-1,map);
    }

    public static void main(String[] args) {
        int[] preorder={3, 9, 8, 5, 4, 10, 20, 15, 7};
        int[] inorder={4, 5, 8, 10, 9, 3, 15, 20, 7};
        Solution solution=new Solution();
        System.out.println( solution.buildTree(preorder,inorder));
    }
}

時間複雜度:O(n)。對於每個節點都有創建過程以及根據左右子樹重建過程。
空間複雜度:O(n)。存儲整棵樹的開銷。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章