Note:《劍指offer》面試題7 重建二叉樹
題目:給出了前序和中序周遊序列,根據這兩個序列重建一棵二叉樹
關鍵點:前序周遊時,根節點爲序列的第一個值;中序周遊時,根節點左邊的序列是根節點的左子樹節點,右邊是右子樹節點
第1、2種方法思路一致,只不過用了Map以提高檢索父節點和子節點index的效率。
思路:遍歷除了第一個值的前序周遊子序列,將所有其他值和根節點值對比,通過在中序周遊序列中它們的index判斷(節點index先於根節點index,則爲其左子樹,反之右子樹,這個判斷由中序周遊的特點決定的)。節點在根節點在其左子樹或右子樹,如果根節點沒有左兒子或者右兒子節點,則該節點成爲其子節點;否則,節點將要對比的新根節點變成原根節點的兒子節點,直到它成爲某個對比的根節點的子節點。時間複雜度應該是O(n*h),n爲節點數,h爲樹的高度。
第一種方法,超時因爲indexOf
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
TreeNode parent = root;
// 從先序周遊開始
for(int i = 1; i < preorder.length; i++){
TreeNode node = new TreeNode(preorder[i]);
while(true){
boolean isLeft = isLeftSubTree(inorder, parent.val, node.val);
if(isLeft){ // 如果是左子樹
if(parent.left == null){ // 左子樹爲空,則它爲父節點的左兒子
parent.left = node;
break;
}else{ // 左子樹有自己的根節點,則將和其對比
parent = parent.left;
}
}else{
if(parent.right == null){
parent.right = node;
break;
}else{
parent = parent.right;
}
}
}
parent = root;
}
return root;
}
/** 節點是root節點的左子樹還是右子樹 **/
private boolean isLeftSubTree(int[] inorder, int root, int node) {
int rootIndex = indexOf(inorder, root);
int nodeIndex = indexOf(inorder, node);
// 如果節點在中序周遊的索引位於根節點的後面,表示它在根節點的右子樹
if(nodeIndex > rootIndex){
return false;
}else if(nodeIndex < rootIndex){ // 反之
return true;
}else{
return false;
} // 按理來說不存在
}
/** 找到某數在數組中的索引 **/
private int indexOf(int[] array, int elem){
for (int i = 0; i < array.length; i++) {
if (elem == array[i])
return i;
}
return -1;
}
第二種方法,103ms
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
TreeNode root = new TreeNode(preorder[0]);
TreeNode parent = root;
Map<Integer, Integer> inorderIndexMap = buildIndexMap(inorder);
// 從先序周遊開始
for(int i = 1; i < preorder.length; i++){
TreeNode node = new TreeNode(preorder[i]);
while(true){
boolean isLeft = isLeftSubTree(inorderIndexMap, parent.val, node.val);
if(isLeft){
if(parent.left == null){
parent.left = node;
break;
}
else{
parent = parent.left;
}
}else{
if(parent.right == null){
parent.right = node;
break;
}
else{
parent = parent.right;
}
}
}
parent = root;
}
return root;
}
private boolean isLeftSubTree(Map<Integer, Integer> inorderIndexMap, int root, int node) {
int rootIndex = inorderIndexMap.get(root);
int nodeIndex = inorderIndexMap.get(node);
// 如果節點在中序周遊的索引位於根節點的後面,表示它在根節點的右子樹
if(nodeIndex > rootIndex){
return false;
}else if(nodeIndex < rootIndex){ // 反之
return true;
}else{
return false;
} // 按理來說不存在
}
/** 用不重複的數組建其值與index的map **/
private Map<Integer, Integer> buildIndexMap(int[] array){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int index = 0;
for(int x : array){
map.put(x, index);
index++;
}
return map;
}
第3種,方法看了《劍指offer》面試題7——重建二叉樹的思路後實現的
思路:在樹的前序遍歷中,根節點爲第一個,後面連續的一部分子序列爲左子樹節點,左子樹序列後面連續的子序列爲右子樹節點。在中序遍歷中,根節點以左序列爲根節點的左子樹,以右序列爲右子樹節點。那麼,在前序周遊序列中得到根節點值,可以通過該值在中序周遊序列中找到其index,那麼index之前爲左子樹,通過計算能得到左右子樹節點數。然後,重建左右子樹,用遞歸的方法重建整個二叉樹。時間複雜度O(n)
第三種方法,6ms
private Map<Integer, Integer> inorderIndexMap = null;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length == 0){
return null;
}
inorderIndexMap = buildIndexMap(inorder);
return buildTree(preorder.length, preorder, 0, inorder, 0);
}
private TreeNode buildTree(int len, int[] preorder, int pStart,
int[] inorder, int iStart) {
if(len <= 0){
return null;
}
TreeNode root = new TreeNode(preorder[pStart]); // 在preorder中第pStart個爲子樹的root
int rootIndex = inorderIndexMap.get(root.val); //找到inorder中子樹root的位置
int leftLen = rootIndex-iStart;
root.left = buildTree(leftLen, preorder, pStart+1, inorder, iStart);
root.right = buildTree(len-1-leftLen, preorder, pStart+1+leftLen, inorder, rootIndex+1);
return root;
}
/** 用不重複的數組建其值與index的map **/
private Map<Integer, Integer> buildIndexMap(int[] array){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int index = 0;
for(int x : array){
map.put(x, index);
index++;
}
return map;
}