1. 程式人生 > >LeetCode題解--105. 從前序與中序遍歷序列構造二叉樹

LeetCode題解--105. 從前序與中序遍歷序列構造二叉樹

1. 題目

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

//前序遍歷 preorder = [3,9,20,15,7]
//中序遍歷 inorder = [9,3,15,20,7]

返回如下的二叉樹:

    3
   / \
  9  20
    /  \
   15   7

2. 分析

    這個題目是以前在本科的資料結構考試中經常出現的一類問題。前序遍歷順序是遍歷根節點,左子樹,右子樹,而中序遍歷則是左子樹,根節點,右子樹,因此這類題目的解題思路是根據前序遍歷的第一個元素確定根節點,然後在中順遍歷中找到根節點的位置。在中序遍歷的左側是左子樹,右側是右子樹。如上面的例子,首先我們根據前序的第一個節點確定3是根節點,那麼在中序遍歷結果中找到3,那麼中序遍歷結果中左側的序列【9】則是3為根節點的左子樹的中序結果,而右側的序列【15,20,7】則是右子樹的中序結果。
    確定了左右子樹,繼續在左子樹的中序遍歷結果中找到出現在先序遍歷結果的元素,因為在先序遍歷結果首先出現的一定是子樹的根節點。如本題,左子樹的中序遍歷結果為【9】,只有一個元素,那麼一定是9先出現在先序的結果中,因此左子樹根節點為9。右子樹的中序遍歷結果為【15,20,7】,那麼首先出現在先序遍歷結果【3,9,20,15,7】的元素是20,那麼20是右子樹的根節點。
    因為左子樹根節點9在其子樹對應的中序結果【9】中沒有左側和右側的序列,那麼9則是一個葉子節點。而右子樹根節點20在其對應子樹的中序結果【15,20,7】中存在左側序列【15】和右側序列【7】,那麼【15】對應的則是以20為根節點的左子樹的中序結果,而【7】則是以20為根節點的右子樹的中序結果。迴圈遞迴上面的過程構造子樹。
    上面的過程是我在資料結構考試中,筆試經常使用的解題思路,反應到程式中需要解決兩個重要的問題


1. 先序遍歷結果的第一個元素(根節點)在中序遍歷結果中的位置如何確定?
2. 左子樹中序遍歷子序列的根節點,即左子樹的根節點如何確定?同樣,右子樹中序遍歷子序列的根節點,即右子樹的根節點如何確定?

3. C++程式一(版本1:leetcode提交超出時間限制)

    這份程式碼能夠通過一些簡單的樹的case,但是如果樹的數量級太大,在leetcode的203個測試中,通過了202個,最後一個測試顯示超時,因為這個演算法的時間複雜度是3次方級!!!
這裡寫圖片描述
    雖然這份程式沒有通過所有測試,但我還是在這裡給出,主要原因是我想通過在這份程式的思路上糾正可以優化的部分。
    也許有的讀者能夠發現,前面的分析中,我始終只是對中序結果進行劃分,而始終在原始的preorder序列中確定位置。
    反觀我們的程式碼中,首先,在中序遍歷結果中確定根節點位置這個過程需要保留,而這個過程的時間複雜度是線性的。程式中主要的時間消耗在於從子樹對應的中序遍歷結果中確定根節點

這個過程。因為需要一層迴圈逐個遍歷中序遍歷子序列的元素,然後在先序結果中逐個確定其位置,確定位置的過程也需要迴圈,則是兩層迴圈,體現在下面的程式碼段中。

for(int i=0;i<left.size();i++){                    
                int index = 0;
                while(left[i]!=preorder[index]) index++;
                if(index<minLeft){
                    leftVal = preorder[index
]; minLeft = index; } }

    再加上遞迴的過程,總的時間複雜度是,其中logn是遞迴的次數,即樹高,n^2則是上面給出的兩層迴圈

n2×logn
    給出這個版本的全部程式碼。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0) return NULL;
        TreeNode* root = new TreeNode(preorder[0]);
        if(preorder.size()==1) return root;
        vector<int> left,right;
        int location = 0;
        while(inorder[location]!=root->val){
            left.push_back(inorder[location]);
            location++;
        }
        location++;
        while(location<inorder.size()){
            right.push_back(inorder[location]);
            location++;
        }        
        buildChild(root,left,right,preorder);
        return root;
    }
    void buildChild(TreeNode* curRoot,vector<int> left,vector<int> right,vector<int> preorder){        
        if(left.size()==0) curRoot->left = NULL;
        if(right.size()==0) curRoot->right = NULL;

        int leftVal,rightVal;
        int minLeft=INT_MAX,minRight=INT_MAX;
        vector<int> leftnew1,rightnew1,leftnew2,rightnew2;
        int location = 0;

        if(left.size()!=0){
            int leftVal;
            int minLeft=INT_MAX;
            vector<int> leftnew1,rightnew1;

            for(int i=0;i<left.size();i++){                    
                int index = 0;
                while(left[i]!=preorder[index]) index++;
                if(index<minLeft){
                    leftVal = preorder[index];
                    minLeft = index;
                }
            }        
            TreeNode* lchild = new TreeNode(leftVal);
            int location = 0;
            while(left[location]!=lchild->val){
                leftnew1.push_back(left[location]);
                location++;
            }
            location++;
            while(location<left.size()){
                rightnew1.push_back(left[location]);        
                location++;
            }
            curRoot->left = lchild;
            buildChild(lchild,leftnew1,rightnew1,preorder);
        }
        if(right.size()!=0){
            int rightVal;
            int minRight=INT_MAX;
            vector<int> leftnew2,rightnew2;

            for(int i=0;i<right.size();i++){
                int index = 0;
                while(right[i]!=preorder[index]) index++;
                if(index<minRight){
                    rightVal = preorder[index];
                    minRight = index;
                }
            }        
            TreeNode* rchild = new TreeNode(rightVal);
            int location = 0;
            while(right[location]!=rchild->val){
                leftnew2.push_back(right[location]);
                location++;
            }
            location++;
            while(location<right.size()){
                rightnew2.push_back(right[location]);
                location++;
            }
            curRoot->right = rchild;
            buildChild(rchild,leftnew2,rightnew2,preorder);
        }                                        
    }
};

4. C++程式二(版本2:leetcode通過程式)

    然而,給定中序遍歷以及先序遍歷結果,能夠確定唯一的樹結構。那麼我們除了對中序遍歷結果遞迴劃分之外,仍然可以對先序遍歷結果進行遞迴劃分。那麼,除了需要確定左右子樹對應的中序遍歷子序列之外,如何確定先序遍歷結果中左右子樹各自對應的部分則是第二個問題,當子樹的先序遍歷結果確定之後,第一個節點則是根節點,就不需要兩層迴圈來確定子樹的根節點了。
    那麼,究竟是如何確定先序遍歷的子樹對應的子序列呢?這裡面存在一個很容易發現的規律。因為我們已經確定了根節點在中序遍歷結果中確定了左子樹和右子樹對應的中序結果,我們便同時確定了其對應的節點個數。
    例如,前面的例子中,左子樹對應的中序結果為【9】,那麼根節點3對應的左子樹則有1個節點。因為先序遍歷是根->左->右的順序,那麼我們只需要在先序結果中根節點後面查1個節點,那麼這個1個節點構成的序列就是左子樹對應的先序結果,那麼後面剩下的就是右子樹對應的先序結果。具體過程如下圖所示:
這裡寫圖片描述
    而遞迴的出口條件是當preorder的size為0,則證明這個子樹已經是樹葉子的NULL部分,直接返回。
    給出這個版本的全部程式碼。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0) return NULL;    //空樹
        TreeNode* root = new TreeNode(preorder[0]);
        if(preorder.size()==1) return root;    //只有一個節點

        vector<int> leftIn,leftPre,rightIn,rightPre;
        int location = 0;
        while(inorder[location]!=root->val){
            leftIn.push_back(inorder[location]);
            location++;
        }
        for(int i=1;i<=location;i++) leftPre.push_back(preorder[i]);
        for(int i=location+1;i<preorder.size();i++){
            rightPre.push_back(preorder[i]);
            rightIn.push_back(inorder[i]);
        }
        root->left = buildChild(leftPre,leftIn);
        root->right = buildChild(rightPre,rightIn);
        return root;
    }
    TreeNode* buildChild(vector<int> preorder,vector<int> inorder){  
        if(preorder.size()==0) return NULL;          //出口條件:preorder為空,則表示這個節點是NULL
        TreeNode* root = new TreeNode(preorder[0]);  //生成當前子樹的根節點
        vector<int> leftIn,leftPre,rightIn,rightPre;
        int location = 0;
        while(inorder[location]!=root->val){
            leftIn.push_back(inorder[location]);
            location++;
        }
        for(int i=1;i<=location;i++) leftPre.push_back(preorder[i]);
        for(int i=location+1;i<preorder.size();i++){
            rightPre.push_back(preorder[i]);
            rightIn.push_back(inorder[i]);
        }
        root->left = buildChild(leftPre,leftIn);
        root->right = buildChild(rightPre,rightIn);
        return root;                                        
    }
};

這裡寫圖片描述