圖文並茂詳解經典算法系列-1:根據指定兩種序列構造二叉樹

0x01.問題

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

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

例如,給出:

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

返回如下的二叉樹:
在這裏插入圖片描述

0x02.算法詳細分析

我們知道,根據前序序列和中序序列是可以完全確定一棵二叉樹,那麼我們是怎麼根據前序序列和中序序列去確定這個這棵樹的呢?我們一起來探究一下。

1.第一個問題:我們要明白,前序序列和中序序列是怎麼得到的?

  • 這應該是比較基礎的二叉樹知識了。

  • 二叉樹的前序序列得到的方式:

    • 遍歷根節點。
    • 遞歸遍歷左子樹。
    • 遞歸遍歷右子樹。
  • 二叉樹的中序序列得到的方式:

    • 遞歸遍歷左子樹。
    • 遍歷根節點。
    • 遞歸遍歷右子樹。

2.第二個問題:前序序列和中序序列中的值是怎樣對應原來樹中的結構的呢?

  • 一定要將這個序列和原來的樹結合起來,知道序列中的值是如何分佈的,才能夠還原原來的那棵二叉樹。

  • 根據上面得到序列的方式,我們應該不難得到,下面的這個關係:
    在這裏插入圖片描述

  • 也就是說,前序序列的根節點在最前面,然後是左子樹,然後是右子樹。

  • 中序序列中先是前序序列,然後是根節點,然後是右子樹。

  • 知道序列中是怎麼分佈後,我們似乎還沒有能夠構造出完整的樹的思路,因爲我們似乎遇到了一個困難:

    • 假如我們第一個以前序序列的第一個節點構造了樹的根節點,下一個節點如何尋找?根節點的左右子樹到底在序列的哪個位置?

3.深度思考一下第三個問題:如何去序列中尋找對應的左右子樹?

  • 既然根節點是前序序列的第一個節點,那麼根節點的左子樹如何尋找?

    • 其實很簡單了,左子樹就是前序序列的第二個節點,因爲前序序列就是先遍歷左子樹,再遍歷右子樹,所以,左子樹就很好尋找了。
  • 那麼右子樹如何去尋找呢?

    • 我們最原始的想法很簡單,就是去找到這個位置就可以了。
      在這裏插入圖片描述
  • 因爲這個位置是右子樹的第一個節點,也就是根節點的右子樹。

  • 那麼問題來了,中間這段距離怎麼求,也就是左子樹的數目。

  • 不要想當然的認爲左子樹和右子樹的數量是相等的,因爲問題只說了是二叉樹,並沒有說是什麼特殊的二叉樹,所以並不能確定左子樹的數量。

  • 這個時候,不要忘記,我們還有一箇中序序列,這個中序序列和前序序列所對應的二叉樹可是相同的,所以自然左子樹的數目也是相同的。

  • 我們的想法是,要是知道根節點在中序序列中的位置,左子樹的數量就可以確定下來了。
    在這裏插入圖片描述

  • 而題目中說了,保證節點的值是唯一的,所以,只要一個個找,肯定是能找到的。

  • 但是這樣查找就比較浪費時間,我們可以維護一個哈希表,每次去哈希表中查找,就能把時間的複雜度降下來。

4.具體的算法思路?

  • 在上面的分析中,我們確定了基本的思路,也就是依次尋找根節點,左子樹,右子樹。

  • 而在樹中,這個過程肯定是可以抽取出來的,也就是可以使用遞歸的方式將這個過程表示出來。

  • 細節: 每一次遞歸需要哪些參數呢?

    • 由於需要確定序列中左右子樹的邊界,所以對於每種序列來說,左右邊界當然是需要的。
  • 細節:初始化?

    • 初始化條件是0,n-1
  • 細節:遞歸退出的條件?

    • 當左邊界大於右邊界時,遞歸退出。

0x03.算法–從前序與中序遍歷序列構造二叉樹

class Solution {

    private Map<Integer,Integer> indexMap;

    private TreeNode toBuildTree(int[] preorder,int[] inorder,int pre_left,int pre_right,int in_left,int in_right){
        //遞歸終止條件
        if(pre_left>pre_right){
            return null;
        }
        //建立根節點
        TreeNode root=new TreeNode(preorder[pre_left]);
        //得到中序序列中根節點的下標
        int in_root=indexMap.get(preorder[pre_left]);
        //得到前序序列中左子樹的長度
        int size_left=in_root-in_left;
        //遞歸建立左子樹
        root.left=toBuildTree(preorder,inorder,pre_left+1,pre_left+size_left,in_left,in_root-1);
        //遞歸建立右子樹
        root.right=toBuildTree(preorder,inorder,pre_left+size_left+1,pre_right,in_root+1,in_right);
        return root;
    }

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n=preorder.length;
        if(n!=inorder.length){
            return null;
        }
        indexMap=new HashMap<>();
        for(int i=0;i<n;i++){
            indexMap.put(inorder[i],i);
        }
        return toBuildTree(preorder,inorder,0,n-1,0,n-1);
    }
}

細節理解:關於邊界參數的變換,都在這張圖裏可以表示出來:

在這裏插入圖片描述

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