本文參考自《劍指offer》一書,代碼採用Java語言。html
更多:《劍指Offer》Java實現合集 java
輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和爲輸入整數的全部路徑。從樹的根結點開始往下一直到葉結點所通過的結點造成一條路徑。node
1.假設找到了其中一條路徑,達到葉結點後,因爲沒有指向父結點的指針,因此必須提早建立一個鏈表存儲前面通過的結點。函數
2.因爲是從根結點出發,因此要想到使用前序遍歷post
3.利用鏈表存儲結點,在該結點完成左右子樹的路徑搜索後(即遞歸函數結束,返回到其父結點以前),要刪除該結點,從而記錄別的路徑。測試
具體實現:經過前序遍歷,從根結點出發,每次在鏈表中存儲遍歷到的結點,若到達葉子結點,則根據全部結點的和是否等於輸入的整數,判斷是否打印輸出。在當前結點訪問結束後,遞歸函數將會返回到它的父結點,因此在函數退出以前,要刪除鏈表中的當前結點,以確保返回父結點時,儲存的路徑恰好是從根結點到父結點。this
改進:書中的代碼是根據全部結點的和是否等於輸入的整數,判斷是否打印輸出。其實沒有這個必要,只須要在每次遍歷到一個結點時,令目標整數等於本身減去當前結點的值,若到達根結點時,最終的目標整數等於0就能夠打印輸出。(描述得不是很清楚,就是至關於每一個結點的目標整數不一樣,詳見代碼)url
測試算例 spa
1.功能測試(一條或者多條對應路徑;無對應路徑;結點值爲正負零;)指針
2.特殊測試(根結點爲null)
//題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和爲輸入整數的所 //有路徑。從樹的根結點開始往下一直到葉結點所通過的結點造成一條路徑。 public class PathInTree { public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } public void findPath(TreeNode root,int target) { if(root==null) return; ArrayList<Integer> list= new ArrayList<>(); printPath(root, target,list); } private void printPath(TreeNode node,int target,ArrayList<Integer> list) { if(node==null) return; list.add(node.val); target-=node.val; //每一個結點的target不會受到方法的影響而改變 if(target==0 && node.left==null && node.right==null) { //葉子結點 for (Integer integer : list) System.out.print(integer+" "); System.out.println(); }else { //中間結點 printPath(node.left, target, list); printPath(node.right, target, list); } list.remove(list.size()-1); }
牛客網中的題目有多兩點要求:1.返回類型爲ArrayList<ArrayList<Integer>>,即返回全部路徑的集合;2.要求返回的集合中,長度大的靠前。下面是實現的代碼:
/* * 幾個要點: * 1. 將nodeList和pathList定義成全局變量,避免在方法中的屢次傳遞 * 2. 在pathList中添加nodeList時,由於nodeList會不斷變化,因此必須新建一個list存入 * 複製ArrayList的方法:newList=new ArrayList<Integer>(oldList)(複製內容,而不是複製地址,注意與newList=oldList的區分) * 3. 在當前結點完成左右子樹的路徑搜索後,記得刪除nodeList中的當前結點 * 4. target是基本數據類型int,不會受到方法的影響而改變 */ private ArrayList<Integer> nodeList= new ArrayList<>(); private ArrayList<ArrayList<Integer>> pathList = new ArrayList<>(); public ArrayList<ArrayList<Integer>> FindPath(TreeNode node,int target) { if(node==null) return pathList; nodeList.add(node.val); target-=node.val; if(target==0 && node.left==null && node.right==null) { //葉子結點 //長度大的nodeList插入到pathList的前面 int i=0; while(i<pathList.size() && nodeList.size()<pathList.get(i).size() ) //記得防止越界 i++; pathList.add(i, new ArrayList<Integer>(nodeList)); //nodeList會隨方法變化,必須新建一個list存入pathList中! }else { //不是葉子結點 pathList=FindPath(node.left, target); pathList=FindPath(node.right, target); } nodeList.remove(nodeList.size()-1); //記得刪除當前結點 return pathList; }
1.二叉樹的許多題目與遍歷(包括層序遍歷)有關,要深入理解;根據根結點的位置判斷使用哪種遍歷。
2.二叉樹遍歷過程沒有父結點指針,要保存路徑的話,必需要建立容器存儲以前的結點。
3.熟悉這道題中在每次遞歸函數結束前刪除當前結點的操做,這能夠確保返回到父結點時路徑恰好是從根結點到父結點。
4.target-=node.val這句代碼很是好,多多體會。
5.牛客網的那部分代碼:在鏈表中存儲一個對象時,若是該對象是不斷變化的,則應該建立一個新的對象複製該對象的內容(而不是指向同一個對象),將這個新的對象存儲到鏈表中。。若是直接存儲該對象的話,鏈表中的對象也會不斷變化。基本數據類型和String則沒有這種問題。說到底實際上是存儲的是地址仍是值的問題。這篇文章討論了一下。