題目描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
二叉樹的前序中序後序遍歷
做這道題之前我們需要先搞懂二叉樹的前序,中序,後序遍歷是怎樣的。
二叉樹是一顆特殊的樹,它的每個節點都不能有多於兩個的兒子節點。即一顆二叉樹最多有兩個兒子。如下圖所示
而樹有三種遍歷方式,分別是前序,中序,後序遍歷。而前,中,後分別表示的是根節點的遍歷順序。
先遍歷根節點,再左節點,再右節點,叫做前序遍歷
先左節點,再根節點,再右節點,叫做中序遍歷
先左節點,再右節點,再根節點,叫做後序遍歷
以上圖所示的二叉樹爲例,分別描述它的前中後序遍歷
前序遍歷過程
它的前序遍歷爲ABCDEFGH
前序遍歷過程爲,先根節點即A,再左節點即B。目前遍歷順序:AB
。此時以B爲根節點的二叉樹,先它的左節點即C,再它的右節點即D。目前遍歷順序:ABCD
。此時以D爲根節點的二叉樹,由於它沒有左節點,所以到它的右節點即E。目前遍歷順序:ABCDE
。到現在整顆二叉樹的根節點A,左節點B已經遍歷完成,再到它的右節點即F。目前遍歷順序:ABCDF
。此時以F爲根節點的二叉樹,先它的左節點即G。目前遍歷順序:ABCDFG
。此時以G爲根節點的,到它的右節點即H。最終遍歷順序爲ABCDFGH
。
中序遍歷過程
中序遍歷爲CBDEAGHF
中序遍歷過程爲,先左節點即B,對於以B爲根節點的二叉樹,又要先它的左節點即C,以C爲根節點的二叉樹沒有兒子節點,所以C是我們遍歷到的第一個節點。目前遍歷順序:C
。此時對於以B爲根節點的二叉樹,已經遍歷完它的左節點,再到它的根節點即B。目前遍歷順序:CB
。再到它的右節點D,以D爲根節點的二叉樹沒有左節點,所以直接它的根節點D,再到它的右節點即E。目前遍歷順序:CBDE
。現在整顆二叉樹的左節點已經遍歷完成,再到它的根節點A。目前遍歷順序:CBDEA
。再到它的右節點F,但是以F爲根節點的二叉樹有左節點G,所以先它的左節點G,以G爲根節點的二叉樹沒有左節點,所以先根節點G,再到右節點H。目前遍歷順序:CBDEAGH
。此時以F爲根節點的二叉樹,它的左節點已經遍歷完,再到它的根節點F。目前遍歷順序:CBDEAGHF
。至此整顆二叉樹遍歷完成
後序遍歷過程
後序遍歷爲CEDBHGFA
後序遍歷過程爲,先左節點即B,對於以B爲根節點的二叉樹,又要先它的左節點即C,以C爲根節點的二叉樹沒有兒子節點,所以C是我們遍歷到的第一個節點。目前遍歷順序:C
。此時對於以B爲根節點的二叉樹,已經遍歷完它的左節點,再到它的右節點即D。以D爲根節點的二叉樹沒有左節點,所以到它的右節點即E。目前遍歷順序:CE
。此時以D爲根節點的二叉樹右節點已經遍歷完,再到它的根節點即D。目前遍歷順序:CED
。此時以B爲根節點的二叉樹左節點右節點均已經遍歷完,再到它的根節點即B。目前遍歷順序:CEDB
。此時整顆二叉樹的左節點已經遍歷完成,再到它的右節點即F。此時以F爲根節點的二叉樹又要先它的左節點G,此時以G爲根節點的二叉樹沒有左節點,所以先右節點即H,再遍歷它的根節點即G。目前遍歷順序:CEDBHG
。此時以F爲根節點的二叉樹,它的左節點已經遍歷完成,同時沒有右節點,所以到它的根節點即F。目前遍歷順序:CEDBHGF
。此時整顆二叉樹,它的左右節點均已遍歷完成,所以到它的根節點A。最終遍歷順序爲:CEDBHGFA
。
解法1
回到這道題目,已知二叉樹的前序中序遍歷序列,要求重構二叉樹。就以前序遍歷{1,2,4,7,3,5,6,8}和中序遍歷{4,7,2,1,5,3,8,6}構成的二叉樹A爲例來講解解題思路。上面有提到前序遍歷是先根節點再左右節點。所以已知A的前序遍歷{1,2,4,7,3,5,6,8},那麼這顆二叉樹的根節點一定是1。而中序遍歷是先左節點,再根節點,再右節點。已知A中序遍歷爲{4,7,2,1,5,3,8,6},且根節點是1。那麼A的左節點是由1左邊的中序序列{4,7,2}構成的二叉樹A1,A的右節點是由1右邊的中序序列{5,3,8,6}構成的二叉樹A2。
因爲A1的中序序列是{4,7,2},元素個數是3,所以A的前序序列{1,2,4,7,3,5,6,8}中,根節點1後面的三個元素即{2,4,7}就是A1的前序序列。所以A2的前序序列就是{2,4,7}後面的所有元素,即{3,5,6,8}
到現在,我們已經把已知A的前序序列和中序序列求A的問題,轉換爲已知A1的前序中序序列求A1,已知A2的前序中序序列求A2的問題。繼續遞歸下去,直到轉換的子樹前序中序序列只有一個元素,就可以直接表示出這顆子樹了。
實現代碼
public class TreeNode
{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode (int x)
{
val = x;
}
}
public TreeNode reConstructBinaryTree(int[] pre, int[] tin)
{
if (!(pre.Length > 0))
{
return null;
}
TreeNode root = new TreeNode(pre[0]);
int index = -1;
// 找到在中序序列中根節點的位置
for (int i = 0; i < tin.Length; i ++)
{
if(tin[i] == pre[0])
{
index = i;
break;
}
}
// 利用根節點的位置,再求出左右兩個子二叉樹的前序中序序列
int[] subLeftPre = new int[index];
int[] subLeftTin = new int[index];
int[] subRightPre = new int[tin.Length - index - 1];
int[] subRightTin = new int[tin.Length - index - 1];
for (int j = 0; j < pre.Length; j ++)
{
if(j > 0)
{
if (j <= index)
{
subLeftPre[j - 1] = pre[j];
}
else
{
subRightPre[j - index - 1] = pre[j];
}
}
if (j < index)
{
subLeftTin[j] = tin[j];
}else if (j > index)
{
subRightTin[j - index - 1] = tin[j];
}
}
if(index > 0)
{
root.left = reConstructBinaryTree(subLeftPre, subLeftTin);
}
if (tin.Length - index - 1 > 0)
{
root.right = reConstructBinaryTree(subRightPre, subRightTin);
}
return root;
}
解法2
上面的解法1,是根據解題思路最直觀的寫法,但是寫法很爛。由於C#對數組沒有取子數組的API。所以求子二叉樹的的前序中序序列寫的太複雜了,需要自己去new前序中序數組,再往這些數組中填寫正確的元素。後來是學習了別人的寫法進行了優化。不用自己去創建子二叉樹的前序中序數組,而是直接用父二叉樹的前序中序序列索引來表示就可以了。
實現代碼
public TreeNode reConstructSubTree(int[] pre, int p1, int p2, int[] tin, int t1, int t2)
{
if (p1 <= p2 && t1 <= t2 && p2 >= 0 && t2 >= 0)
{
TreeNode root = new TreeNode(pre[p1]);
for (int i = t1; i <= t2; i++)
{
if (tin[i] == pre[p1])
{
root.left = reConstructSubTree(pre, p1 + 1, p1 + i - t1, tin, t1, i - 1);
root.right = reConstructSubTree(pre, p1 + i + 1 - t1, p2, tin, i + 1, t2);
return root;
}
}
}
return null;
}
public TreeNode reConstructBinaryTreeOptimize(int[] pre, int[] tin)
{
return reConstructSubTree(pre, 0, pre.Length - 1, tin, 0, tin.Length - 1);
}
遇到的問題
在使用解法1解這個問題的時候,一直只能通過80%多的測試用例,花了很久的時間去查爲什麼。問題原因是由於希望提高效率,所以就在一個循環中構建出子二叉樹的前序中序序列,但是邏輯上有bug,數組元素少賦值了,又由於新建的數組都是有默認值0的,導致序列實際上已經求解錯誤了,但又不易被發現。
遞歸問題是將大問題,轉換爲小問題,一直轉換爲小問題可以直接求解爲止。我的這個bug其實並不是不容易被發現,直接代入最小問題就可以發現的,比如前序序列爲{1, 2, 3},中序序列爲{2,1,3}。總結下來,以後對於遞歸問題,一定要先保證最小問題的解法是正確的,否則根基是錯誤的,最後得到的答案也一定是錯誤的。
最後放上有bug的代碼,注意是有bug的
public TreeNode reConstructBinaryTree(int[] pre, int[] tin)
{
if (!(pre.Length > 0))
{
return null;
}
TreeNode root = new TreeNode(pre[0]);
int index = -1;
for (int i = 0; i < tin.Length; i ++)
{
if(tin[i] == pre[0])
{
index = i;
break;
}
}
int[] subLeftPre = new int[index];
int[] subLeftTin = new int[index];
int[] subRightPre = new int[tin.Length - index - 1];
int[] subRightTin = new int[tin.Length - index - 1];
// bug 這裏j從1開始,會導致中序序列元素被少遍歷一次,導致少賦值
for (int j = 1; j < pre.Length; j ++)
{
if (j <= index)
{
subLeftPre[j - 1] = pre[j];
}
else
{
subRightPre[j - index - 1] = pre[j];
}
if (j - 1 < index)
{
subLeftTin[j - 1] = tin[j - 1];
}else if (j - 1 > index)
{
subRightTin[j - 2 - index] = tin[index + j - 1 - index];
}
}
if(index > 0)
{
root.left = reConstructBinaryTree(subLeftPre, subLeftTin);
}
if (tin.Length - index - 1 > 0)
{
root.right = reConstructBinaryTree(subRightPre, subRightTin);
}
return root;
}