pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树
(給算法愛好者加星標,修煉編程內功)
來源:?數據結構和算法-山大王wld
問題描述
今天我們就不做關于雙指針的了,我們爬到樹上玩會兒,做一道關于二叉樹的題。今天的題就一句話,根據一棵樹的前序遍歷與中序遍歷構造二叉樹。
注意:
你可以假設樹中沒有重復的元素。
例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
? ? 3
? ?/ \
? 9? 20
? ? /? \
? ?15? ?7
問題分析
做這題之前我們先來看一下樹的幾種遍歷順序。
先序遍歷:根節點→左子樹→右子樹。
中序遍歷:左子樹→根節點→右子樹。
后續遍歷:左子樹→右子樹→根節點。
其實也很好記,他是根據根節點遍歷的順序來定義的,比如先遍歷根節點就是先序遍歷,中間遍歷根節點就是中序遍歷,最后遍歷根節點就是后續遍歷,至于左子樹和右子樹哪個先遍歷,記住一點,這3種遍歷順序右節點永遠都不可能比左節點先遍歷。
我們就以上面的示例數據來看下,前序遍歷是[3,9,20,15,7],前序遍歷先訪問的是根節點,所以3就是根節點。中序遍歷是[9,3,15,20,7],由于中序遍歷是在左子樹都遍歷完的時候才遍歷根節點,所有在中序遍歷中3前面的都是3的左子樹節點,3后面的都是3的右子樹節點。也就是下面這樣
然后我們再使用同樣的方式對左右子樹繼續劃分,一直這樣下去,直到不能再分為止,我們來看下代碼
1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{2????//把前序遍歷的值和中序遍歷的值放到list中
3????List?preorderList?=?new?ArrayList<>(); 4????List?inorderList?=?new?ArrayList<>(); 5????for?(int?i?=?0;?i? 6????????preorderList.add(preorder[i]); 7????????inorderList.add(inorder[i]); 8????} 9????return?helper(preorderList,?inorderList);10}1112private?TreeNode?helper(List?preorderList,?List?inorderList)?{13????if?(inorderList.size()?==?0)14????????return?null;15????//前序遍歷的第一個值就是根節點16????int?rootVal?=?preorderList.remove(0);17????//創建跟結點18????TreeNode?root?=?new?TreeNode(rootVal);19????//查看根節點在中序遍歷中的位置,然后再把中序遍歷的數組劈兩半,前面部分是20????//根節點左子樹的所有值,后面部分是根節點右子樹的所有值21????int?mid?=?inorderList.indexOf(rootVal);22????//[0,mid)是左子樹的所有值,inorderList.subList(0,?mid)表示截取inorderList23????//的值,截取的范圍是[0,mid),包含0不包含mid。24????root.left?=?helper(preorderList,?inorderList.subList(0,?mid));25????//[mid+1,inorderList.size())是右子樹的所有值,26????//?inorderList.subList(mid?+?1,?inorderList.size())表示截取inorderList27????//的值,截取的范圍是[mid+1,inorderList.size()),包含mid+1不包含inorderList.size()。28????root.right?=?helper(preorderList,?inorderList.subList(mid?+?1,?inorderList.size()));29????return?root;30}
上面代碼中是先把數組轉化為list集合,然后在list集合中進行截取,這樣效率明顯不是很高,實際上我們還可以不使用list,不對數組進行截取。
使用指針解決
我們只需要使用3個指針即可。一個是preStart,他表示的是前序遍歷開始的位置,一個是inStart,他表示的是中序遍歷開始的位置。一個是inEnd,他表示的是中序遍歷結束的位置,我們主要是對中序遍歷的數組進行拆解,下面就以下面的這棵樹來畫個圖分析下
他的前序遍歷是:[3,9,8,5,2,20,15,7]
他的中序遍歷是:[5,8,9,2,3,15,20,7]
這里只要找到了前序遍歷的結點在中序遍歷的位置,我們就可以把中序遍歷數組分解為兩部分了。如果index是前序遍歷的某個值在中序遍歷數組中的索引,以index為根節點劃分的話,那么中序遍歷中
[0,index-1]就是根節點左子樹的所有節點,
[index+1,inorder.length-1]就是根節點右子樹的所有節點。
中序遍歷好劃分,那么前序遍歷呢,如果是左子樹:
preStart=index+1;
如果是右子樹就稍微麻煩點,
preStart=preStart+(index-instart+1);
preStart是當前節點比如m先序遍歷開始的位置,index-instart+1就是當前節點m左子樹的數量加上當前節點的數量,所以preStart+(index-instart+1)就是當前節點m右子樹前序遍歷開始的位置,我們來看下完整代碼
1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{2????return?helper(0,?0,?inorder.length?-?1,?preorder,?inorder);
3}
4
5public?TreeNode?helper(int?preStart,?int?inStart,?int?inEnd,?int[]?preorder,?int[]?inorder)?{
6????if?(preStart?>?preorder.length?-?1?||?inStart?>?inEnd)?{
7????????return?null;
8????}
9????//創建結點
10????TreeNode?root?=?new?TreeNode(preorder[preStart]);
11????int?index?=?0;
12????//找到當前節點root在中序遍歷中的位置,然后再把數組分兩半
13????for?(int?i?=?inStart;?i?<=?inEnd;?i++)?{
14????????if?(inorder[i]?==?root.val)?{
15????????????index?=?i;
16????????????break;
17????????}
18????}
19????root.left?=?helper(preStart?+?1,?inStart,?index?-?1,?preorder,?inorder);
20????root.right?=?helper(preStart?+?index?-?inStart?+?1,?index?+?1,?inEnd,?preorder,?inorder);
21????return?root;
22}
使用棧解決
如果使用棧來解決首先要搞懂一個知識點,就是前序遍歷挨著的兩個值比如m和n,他們會有下面兩種情況之一的關系。
1,n是m左子樹節點的值。
2,n是m右子樹節點的值或者是m某個祖先節點的右節點的值。
對于第一個知識點我們很容易理解,如果m的左子樹不為空,那么n就是m左子樹節點的值。
對于第二個問題,如果一個結點沒有左子樹只有右子樹,那么n就是m右子樹節點的值,如果一個結點既沒有左子樹也沒有右子樹,那么n就是m某個祖先節點的右節點,我們只要找到這個祖先節點就好辦了。
搞懂了這點,代碼就很容易寫了,下面看下完整代碼
1public?TreeNode?buildTree(int[]?preorder,?int[]?inorder)?{2????if?(preorder.length?==?0)
3????????return?null;
4????Stack?s?=?new?Stack<>(); 5????//前序的第一個其實就是根節點 6????TreeNode?root?=?new?TreeNode(preorder[0]); 7????TreeNode?cur?=?root; 8????for?(int?i?=?1,?j?=?0;?i? 9????????//第一種情況10????????if?(cur.val?!=?inorder[j])?{11????????????cur.left?=?new?TreeNode(preorder[i]);12????????????s.push(cur);13????????????cur?=?cur.left;14????????}?else?{15????????????//第二種情況16????????????j++;17????????????//找到合適的cur,然后確定他的右節點18????????????while?(!s.empty()?&&?s.peek().val?==?inorder[j])?{19????????????????cur?=?s.pop();20????????????????j++;21????????????}22????????????//給cur添加右節點23????????????cur?=?cur.right?=?new?TreeNode(preorder[i]);24????????}25????}26????return?root;27}
總結
這題如果直接在紙上推算出來還是很簡單的,如果寫成代碼就稍微有一點難度。當然第一種寫法還是非常簡單,他是每次遍歷都會把數組截取,但截取效率不高,所以第二種方式就使用指針的方式,每次遍歷的時候通過指針來固定左子樹和右子樹在數組中的范圍。第3種方式是巧妙的運用了前序遍歷的特點,然后使用棧的方式解決,這種方式也是非常經典的,一般不太容易想到。
- EOF -
推薦閱讀??點擊標題可跳轉1、算法題400:二叉樹的鋸齒形層次遍歷
2、算法題367:二叉樹的最大深度
3、完全二叉樹的節點數,你真的會算嗎?
覺得本文有幫助?請分享給更多人
關注「算法愛好者」加星標,修煉編程內功
好文章,我在看??
總結
以上是生活随笔為你收集整理的pat根据中序遍历和先序遍历_算法题399:从前序与中序遍历序列构造二叉树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国手枪之王,54式为何有颗大黑星
- 下一篇: 日本为什么会发动全面侵华战争