Leetcode:Construct Binary Tree from Preorder and Inorder Traversal
題目大意:分別按先序和中序遍歷同一個n結點二叉樹,得到兩個結點數組P和I。要求利用這些結點數組還原二叉樹。
這道題考驗對二叉樹的理解。先說明一些基礎的知識:
先序遍歷表示當訪問一個結點時,先訪問結點值,再訪問結點的左孩子,最後訪問結點的右孩子。
中序遍歷表示當訪問一個結點時,先訪問結點的左孩子,再訪問結點值,最後訪問結點的右孩子。(我一開始把先序和中序搞混了)
接下來開始推導一些有用的性質,這裏使用p和i分別表示兩個函數,對於二叉樹結點n,p(n)表示通過先序遍歷過程中n的值被訪問的次序,i(n)表示通過中續遍歷n的值被訪問的次序。對於二叉樹的根結點root,有p(root)=1。
對於兩個結點A和B,假設二者最低的公共祖先結點為C。
性質1:若i(A)<i(B),則必然有以下三個命題之一成立:
A是C的左孩子的後代且B是C的右孩子的後代。(任意一個結點都是自己的後代)
A=C,且B是C的右孩子的後代。
B=C,且A是C的左孩子的後代。
性質2:若p(B)<p(A),則必然有下面三個命題之一成立:
B=C。
B是C的左孩子且A是C的右孩子。
性質3:若A是B的後代,且p(A)=P(B)+1,則必然有命題成立:
A是B的左孩子。
定義1:假設有結點序列F[0],F[1],...,F[k],其中F[0]=B,且F[i]是F[i-1]的父親,若t是第一個滿足條件F[t-1]是F[t]的左孩子的下標,那麽稱F[t]是B的最近左父。
性質4:若D是B的最近左父,且滿足p(B)<p(A),i(B)<i(A)<i(D),那麽必然有下面命題成立:
A是B的右孩子的後代。這裏稍微說明下:由於i(B)<i(A)<i(D)可以導出A和B是D的左孩子的後代,且A不可能是B的右孩子,而p(B)<p(A)可以導出A不可能是B的父親。
性質5:若A是B的右孩子的後代,且對於所有滿足p(B)<p(X)<p(A)的結點X,都是B的左孩子及其後代,那麽A是B的右孩子。
上面所有前提下的推論可以通過枚舉討論得到,這裏不細加推導。
現在先說明如何判斷一個結點是另外一個結點的左孩子。對於結點A和B,若p(B)+1=p(A)且i(A)<i(B),那麽必然有A是B的左孩子。由於性質1和性質2同時被滿足,而唯一不矛盾的結論就是B=C且A是B的左孩子的後代,而利用性質3可以得到A是B的左孩子。實際上二者是等價條件。
而對於如何判斷一個結點是另外一個結點的右孩子,前面的性質4和性質5已經說的很詳細了。
最後說明算法的執行流程。先用一個哈希表實現函數i,之後利用一個可回退的叠代器ITER按順序遍歷數組I。我們建立一個根結點R,假設我們的結果二叉樹的根結點是R的左孩子,並認為其i值為I的長度(即先序遍歷中被最後訪問),這樣可以保證每個結點的最近左父的存在。之後利用遞歸和ITER建立R的左孩子(即結果二叉樹)。下面給出建立左右結點的代碼:
BuildLeft(ITER, i, father)
if(ITER.hasNext() == false)
return NULL
A = ITER.next()
if(i(A)<i(father) == false)
ITER.previous()
return NULL
A.left = BuildLeft(ITER, i, A)
A.right = BuildRight(ITER, i, A, father)
return A
BuildRight(ITER, i, father, nlAncestor)
if(ITER.hasNext() == false)
return NULL
A = ITER.next()
if(i(father)<i(A) && i(A)<i(nlAncestor) == false)
ITER.previous()
return NULL
A.left = BuildLeft(ITER, i, A)
A.right = BuildRight(ITER, i, A, nlAncestor)
return A
兩個函數均通過遞歸和一些判斷對二叉樹進行建立。
最後說明一下時間復雜度,對於每一次BuildLeft和BuildRight被調用,都會引起其一次校驗不通過或是二叉樹中一個結點的建立。校驗不通過的情況下,其father必定被加入到了二叉樹中,而每一個father最多有兩次調用BuildLeft和BuildRight校驗不通過的情況,我們將這兩次的費用追加在father建立的費用上,由於都是常量級別的費用,因此可以認為每次結點建立都是一個常量時間復雜度的費用。而整個流程最多n個結點被建立加入到二叉樹中,故總的時間費用為O(n)。
最後上實際代碼,代碼的前面兩行是由於一開始弄混淆了先序和中序:
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 12 13 public TreeNode buildTree(int[] preorder, int[] inorder) { 14 int[] tmp = preorder; 15 preorder = inorder; 16 inorder = tmp; 17 18 Map<Integer, Integer> p = new HashMap(); 19 List<Integer> list = new ArrayList<Integer>(inorder.length); 20 for (int i = 0, bound = preorder.length; i < bound; i++) { 21 Integer v = preorder[i]; 22 p.put(v, i); 23 } 24 25 for(int i = 0, bound = inorder.length; i < bound; i++) 26 { 27 Integer v = inorder[i]; 28 list.add(v); 29 } 30 31 return buildLeft(list.listIterator(), p, preorder.length); 32 } 33 34 public TreeNode buildLeft(ListIterator<Integer> iter, Map<Integer, Integer> p, int parentIndex) { 35 if (!iter.hasNext()) { 36 return null; 37 } 38 39 Integer rootVal = iter.next(); 40 int rootIndex = p.get(rootVal); 41 if (rootIndex > parentIndex) { 42 iter.previous(); 43 return null; 44 } 45 46 TreeNode root = new TreeNode(rootVal); 47 root.left = buildLeft(iter, p, rootIndex); 48 root.right = buildRight(iter, p, rootIndex, parentIndex); 49 50 return root; 51 } 52 53 public TreeNode buildRight(ListIterator<Integer> iter, Map<Integer, Integer> p, int parentIndex, int lastLeftParentIndex) { 54 if (!iter.hasNext()) { 55 return null; 56 } 57 58 Integer rootVal = iter.next(); 59 int rootIndex = p.get(rootVal); 60 if (!(parentIndex < rootIndex && rootIndex < lastLeftParentIndex)) { 61 iter.previous(); 62 return null; 63 } 64 65 TreeNode root = new TreeNode(rootVal); 66 root.left = buildLeft(iter, p, rootIndex); 67 root.right = buildRight(iter, p, rootIndex, lastLeftParentIndex); 68 69 return root; 70 } 71 }View Code
Leetcode:Construct Binary Tree from Preorder and Inorder Traversal