1. 程式人生 > 其它 >106. 從中序與後序遍歷序列構造二叉樹

106. 從中序與後序遍歷序列構造二叉樹

106. 從中序與後序遍歷序列構造二叉樹

題目連結:106. 從中序與後序遍歷序列構造二叉樹(中等)

根據一棵樹的中序遍歷與後序遍歷構造二叉樹。

注意: 你可以假設樹中沒有重複的元素。

例如,給出

中序遍歷 inorder = [9,3,15,20,7]
後序遍歷 postorder = [9,15,7,20,3]

返回如下的二叉樹:

   3
/ \
9 20
/ \
15 7

解題思路

膜拜大佬:圖解構造二叉樹之中序+後序

前提

解決此問題的關鍵在於要很熟悉樹的各種遍歷次序代表的什麼,最好能夠將圖畫出來。本題解帶你先進行中序遍歷和後續遍歷二叉樹,然後再根據遍歷結果將二叉樹進行還原。

首先,來一棵樹

然後再看樹的遍歷結果

根據中序和後序遍歷結果還原二叉樹

中序遍歷和後續遍歷的特性

首先來看題目給出的兩個已知條件 中序遍歷序列後序遍歷序列 根據這兩種遍歷的特性我們可以得出兩個結論

  1. 在後序遍歷序列中,最後一個元素為樹的根節點

  2. 在中序遍歷序列中,根節點的左邊為左子樹,根節點的右邊為右子樹

如下圖所示

樹的還原過程描述

根據中序遍歷和後續遍歷的特性我們進行樹的還原過程分析

  1. 首先在後序遍歷序列中找到根節點(最後一個元素)

  2. 根據根節點在中序遍歷序列中找到根節點的位置

  3. 根據根節點的位置將中序遍歷序列分為左子樹和右子樹

  4. 根據根節點的位置確定左子樹和右子樹在中序陣列和後續陣列中的左右邊界位置

  5. 遞迴構造左子樹和右子樹

  6. 返回根節點結束

樹的還原過程變數定義

需要定義幾個變數幫助我們進行樹的還原

  1. unordered_map inorderMap 需要一個雜湊表來儲存中序遍歷序列中,元素和索引的位置關係.因為從後序序列中拿到根節點後,要在中序序列中查詢對應的位置,從而將陣列分為左子樹和右子樹

  2. int ri 根節點在中序遍歷陣列中的索引位置

  3. 中序遍歷陣列的兩個位置標記 [is, ie]is 是起始位置,ie是結束位置

  4. 後序遍歷陣列的兩個位置標記 [ps, pe] ps 是起始位置,pe 是結束位置

位置關係的計算

在找到根節點位置以後,我們要確定下一輪中,左子樹和右子樹在中序陣列和後續陣列中的左右邊界的位置。

  1. 左子樹-中序陣列 is = is, ie = ri - 1

  2. 左子樹-後序陣列 ps = ps, pe = ps + ri - is - 1 (pe計算過程解釋,後續陣列的起始位置加上左子樹長度-1 就是後後序陣列結束位置了,左子樹的長度 = 根節點索引-左子樹)

  3. 右子樹-中序陣列 is = ri + 1, ie = ie

  4. 右子樹-後序陣列ps = ps + ri - is, pe - 1

聽不明白沒關係,看圖就對了,計算圖示如下

樹的還原過程

程式碼

C++

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int value) : val(value), left(nullptr), right(nullptr) {}
};
​
//遞迴
class Solution {
public:
    unordered_map<int,int> inorderMap;
    vector<int> post;
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        for (int i = 0; i < inorder.size(); i++) {
            // 將中序遍歷的節點值和索引記錄在雜湊表中
            inorderMap.insert(pair<int,int>(inorder[i],i));
        }
        post = postorder; //
        TreeNode* root = getTree(0, inorder.size() - 1, 0, postorder.size() - 1);
        return root;
    }
    /**
     * 根據邊界構建樹
     * @param is inorderStart
     * @param ie inorderEnd
     * @param ps postorderStart
     * @param pe postorderEnd
     * @return
     */
    TreeNode* getTree(int is, int ie, int ps, int pe){
        if (is > ie || ps > pe) return nullptr;
        int nodeVal = post[pe]; // 根據後序遍歷的結果取得根節點
        //int ri = inorderMap.find(nodeVal)->second; // 用下面一條語句也許
        int ri = inorderMap[nodeVal]; // 得到 根節點 在中序遍歷陣列中的下標。
        TreeNode* node = new TreeNode(nodeVal);
        node->left = getTree(is, ri - 1, ps, ps + ri - 1 -is);
        node->right = getTree(ri + 1, ie, ps + ri - is, pe - 1);
        // 注意 返回的是新建立的 node 節點
        return node;
    }
};

JavaScript

//建構函式
function TreeNode(val, left, right) {
    this.val = (val === undefined ? 0 : val);
    this.left = (left === undefined ? null : left);
    this.right = (right === undefined ? null : right);
}
​
let inorderMap = {};
let post = [];
/**
 * @param {number[]} inorder
 * @param {number[]} postorder
 * @return {TreeNode}
 */
var buildTree = function(inorder, postorder) {
    for (let i = 0; i < inorder.length; i++) {
        // 將中序遍歷的節點值和索引記錄在雜湊表中
        inorderMap[inorder[i]] = i;
    }
    post = postorder;
    return getTree(0, inorder.length - 1, 0, postorder.length - 1);
};
​
var node1 = buildTree([4, 2, 8, 5, 9, 1, 6, 10, 3, 7], [4, 8, 9, 5, 2, 10, 6, 7, 3, 1]);
console.log(node1);
​
/**
 * 根據邊界構建樹
 * @param is inorderStart
 * @param ie inorderEnd
 * @param ps postorderStart
 * @param pe postorderEnd
 * @return {TreeNode}
 */
function getTree(is, ie, ps, pe) {
    if (is > ie || ps > pe) return null;
    let nodeVal = post[pe];
    let ri = inorderMap[nodeVal];
    let node = new TreeNode(nodeVal);
    //let node = { val: nodeval }; // 沒有用建構函式,直接是物件
    node.left = getTree(is, ri - 1, ps, ps + ri - 1 - is);
    node.right = getTree(ri + 1, ie, ps + ri - is, pe - 1);
    return node;
}