LeetCode105-從前序與中序遍歷序列構造二叉樹

LeetCode105-從前序與中序遍歷序列構造二叉樹

最近全國疫情嚴重,待在家裏沒事幹,馬上又要準備春招了,最近刷刷題,記錄一下!再說一句,武漢加油,大家出門記得戴口罩!

1、題目

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

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

2、思路

如何遍歷一棵樹

有兩種通用的遍歷樹的策略:

  • 寬度優先搜索(BFS):我們按照高度順序一層一層的訪問整棵樹,高層次的節點將會比低層次的節點先被訪問到。
  • 深度優先搜索(DFS):在這個策略中,我們採用深度作爲優先級,以便從跟開始一直到達某個確定的葉子,然後再返回根到達另一個分支。
    深度優先搜索策略又可以根據根節點、左孩子和右孩子的相對順序被細分爲前序遍歷,中序遍歷和後序遍歷。
    我們使用哈希表實現(遞歸) O(n)
    遞歸建立整棵二叉樹:先遞歸創建左右子樹,然後創建根節點,並讓指針指向兩棵子樹。

具體步驟如下:

  • 先利用前序遍歷找根節點:前序遍歷的第一個數,就是根節點的值;
  • 在中序遍歷中找到根節點的位置 k,則 k 左邊是左子樹的中序遍歷,右邊是右子樹的中序遍歷;
  • 假設左子樹的中序遍歷的長度是 l,則在前序遍歷中,根節點後面的 l 個數,是左子樹的前序遍歷,剩下的數是右子樹的前序遍歷;
  • 有了左右子樹的前序遍歷和中序遍歷,我們可以先遞歸創建出左右子樹,然後再創建根節點;
    如圖所示:
    在這裏插入圖片描述
    時間複雜度分析:我們在初始化時,用哈希表(unordered_map<int,int>)記錄每個值在中序遍歷中的位置,這樣我們在遞歸到每個節點時,在中序遍歷中查找根節點位置的操作,只需要 O(1) 的時間。此時,創建每個節點需要的時間是 O(1),所以總時間複雜度是 O(n)。
    1. 從左到右遍歷preorder、inorder
    2. preorder的第一個元素一定是根元素
      2.1. 找到根元素後,可以在inorder中區分左、右子樹
      2.2. 當前根的左子樹範圍:inorder[inorder未查找的最左邊(從0開始), 和根元素相等的索引位置)
      2.3. 當前根的左子樹範圍:preorder(當前根索引位置(從0開始), 當前根的索引位置 + inorder已知左子樹長度]
           原因:因爲preorder、inorder中左、右子樹的長度相等(只是觀察得出,爲什麼這麼巧,還沒想透徹)
    3. 當preorder的指針向右移動到"左子樹長度",說明當前根節點的左子樹已經處理完畢
    4. 遞歸開始查找,如果沒有超出左子樹範圍,preorder指針向右移動一位繼續搜索
      4.1. 此時的節點最多隻可能有2種身份:A + B
        A. 根節點
        B. 左節點或右節點
      4.2. inorder的起始節點是"最左的節點",當preorder中的值與他相等時,可以判斷無後續數據,結束搜索
      4.3  inorder指針向右移動一位(排除已使用節點),縮小搜索範圍
    5. 右節點確定規則:因爲上一步確定的是一個左節點,preorder順序爲根左右,所以preorder的下一個節點就是右節點

3、代碼

遞歸實現:

class Solution {
public:
    //定義哈希表存儲中序遍歷中每個點的位置
    unordered_map<int,int> Hash;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //計算整顆子樹的大小
        int n=preorder.size();
        //哈希表存儲中序遍歷中每個點的位置
        for(int i=0;i<n;i++)
        {
            Hash[inorder[i]]=i;
        }
        return dfs(preorder,inorder,0,n-1,0,n-1);
    }
    TreeNode* dfs(vector<int>& preorder,vector<int>& inorder,int pl,int pr,int il,int ir)
    {
        if (pl > pr) return NULL;
        //根節點的值
        int val=preorder[pl];
        int k=Hash[val];//距離
        int len=k-il;
        //定義根節點
        auto root=new TreeNode(val);
        root->left=dfs(preorder,inorder,pl+1,pl+len,il,k-1);
        root->right=dfs(preorder,inorder,pl+len+1,pr,k+1,ir);
        return root;
    }
};

Java

class Solution {
    //定義哈希表,存儲中序遍歷每個點的位置
    HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            map.put(inorder[i], i);
        }
        return dfs(preorder, 0, preorder.length, inorder, 0, inorder.length);  
    }
    private TreeNode dfs(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
        // preorder 爲空,直接返回 null
        if (p_start == p_end) {
            return null;
        }
        int root_val = preorder[p_start];
        TreeNode root = new TreeNode(root_val);
        //在中序遍歷中找到根節點的位置
        
        //int i_root_index = 0;
        /*
        for (int i = i_start; i < i_end; i++) {
            if (root_val == inorder[i]) {
                i_root_index = i;
                break;
            }
        }
        */
        int i_root_index = map.get(root_val);
        //k          i_root_index
        //leftNum    len
        int leftNum = i_root_index - i_start;
        //遞歸的構造左子樹
        root.left =dfs(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start,i_root_index);
        //遞歸的構造右子樹
        root.right =dfs(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
        return root;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章