LeetCode演算法題-Binary Tree Level Order Traversal II(Java實現)
這是悅樂書的第165次更新,第167篇原創
01 看題和準備
今天介紹的是LeetCode演算法題中Easy級別的第24題(順位題號是107)。給定二叉樹,返回其節點值的自下而上級別順序遍歷(即從左到右,逐層逐層)。例如:
給定二叉樹[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其自下而上的級別順序遍歷:[[15,7],[9,20],[3]]。
本次解題使用的開發工具是eclipse,jdk使用的版本是1.8,環境是win7 64位系統,使用Java語言編寫和測試。
02 第一種解法
特殊情況:當傳入的二叉樹為空時,返回我們新定義的空List即可。
正常情況:從示例最後要求輸出的結果來看,根節點是陣列的最後一位元素,如果是自頂向下遍歷節點,我們可以使用佇列,藉助其先進先出的特點,一層一層遍歷節點,將每一層遍歷的節點值存入List中,再將List放入List2中,最後再倒序遍歷List2存入List3中,List3就是最後的結果。
public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> list2 = new ArrayList<>(); if (root == null) { return list2; } List<Integer> list = new ArrayList<Integer>(); Queue<TreeNode> q = new LinkedList<>(); q.add(root); while (!q.isEmpty()) { list = new ArrayList<Integer>(); Queue<TreeNode> tem = new LinkedList<>(); while (!q.isEmpty()) { TreeNode t = q.poll(); list.add(t.val); if (t.left != null) { tem.add(t.left); } if (t.right != null) { tem.add(t.right); } } list2.add(list); q = tem; } List<List<Integer>> list3 = new ArrayList<>(); for(int i=list2.size()-1; i >= 0; i--){ list3.add(list2.get(i)); } return list3; }
遍歷節點的處理方法和昨天那道求最長路徑的寫法類似,藉助佇列先進先出的特點,從左往右依次迴圈。
03 第二種解法
我們可以將第一種解法再優化下,不再建立新的佇列來存新的一層的節點。和昨天那道題的寫法類似,藉助佇列的size來作為判斷條件。
public List<List<Integer>> levelOrderBottom2(TreeNode root) { List<List<Integer>> list2 = new ArrayList<>(); if (root == null) { return list2; } List<Integer> list = new ArrayList<Integer>(); Queue<TreeNode> q = new LinkedList<>(); q.offer(root); while (!q.isEmpty()) { list = new ArrayList<Integer>(); int n = q.size(); while (n-- > 0) { TreeNode t = q.poll(); list.add(t.val); if (t.left != null) { q.offer(t.left); } if (t.right != null) { q.offer(t.right); } } list2.add(list); } List<List<Integer>> list3 = new ArrayList<>(); for(int i=list2.size()-1; i >= 0; i--){ list3.add(list2.get(i)); } return list3; }
04 第三種解法
上面兩種解法都是使用新的List來儲存List2倒序遍歷的值作為最後結果返回,既然是先進後出的特點,我們可以使用棧來儲存每遍歷一層節點存入的List,再使用棧的pop方法出棧存入新的List。
public List<List<Integer>> levelOrderBottom3(TreeNode root) {
List<List<Integer>> list2 = new ArrayList<>();
if (root == null) {
return list2;
}
List<Integer> list = new ArrayList<Integer>();
Stack<List<Integer>> stack = new Stack<List<Integer>>();
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
list = new ArrayList<Integer>();
int n = q.size();
while (n-- > 0) {
TreeNode t = q.poll();
list.add(t.val);
if (t.left != null) {
q.offer(t.left);
}
if (t.right != null) {
q.offer(t.right);
}
}
stack.add(list);
}
while (!stack.isEmpty()) {
list2.add(stack.pop());
}
return list2;
}
05 第四種解法
上面的三種都是使用遍歷的方式,那我們可不可以使用遞迴的方法遍歷每一層的節點值?顯然是可以的,我們可以使用層數作為標記,來判斷現階段處於那一層,從而來決定是新建一個List還是取已存在的List往裡面存入新的節點值。
public List<List<Integer>> levelOrderBottom4(TreeNode root) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<List<Integer>> node = new ArrayList<List<Integer>>();
viewTree(root, res, 0);
for (int i=res.size()-1; i>=0; i--) {
node.add(res.get(i));
}
return node;
}
public void viewTree(TreeNode root, List<List<Integer>> res, int deep) {
if(root == null)
return;
if (res.size() <= deep) {
List<Integer> node = new ArrayList<Integer>();
node.add(root.val);
res.add(node);
} else {
List<Integer> node = (List<Integer>)res.get(deep);
node.add(root.val);
}
viewTree(root.left, res, deep+1);
viewTree(root.right, res, deep+1);
}
從根節點開始,先遞迴獲取根節點左子節點及其子節點的節點值,然後再遞迴獲取根節點右子節點及其子節點的節點值。
06 驗證與測試
對於上面四種解法,我們選取了一個四層的二叉樹來作為測試資料,測試程式碼如下:
public static void main(String[] args) {
Easy_107_BinaryTreeLevelOrderTraversalII instance = new Easy_107_BinaryTreeLevelOrderTraversalII();
TreeNode t = new TreeNode(1);
TreeNode t2 = new TreeNode(2);
TreeNode t3 = new TreeNode(3);
TreeNode t4 = new TreeNode(4);
TreeNode t5 = new TreeNode(5);
TreeNode t6 = new TreeNode(6);
TreeNode t7 = new TreeNode(7);
TreeNode t8 = new TreeNode(8);
t.left = t2;
t.right = t3;
t2.left = t4;
t2.right = t5;
t3.left = t6;
t3.right = t7;
t7.left = t8;
long start = System.nanoTime();
List<List<Integer>> result = instance.levelOrderBottom(t);
long end = System.nanoTime();
System.out.println("levelOrderBottom---輸出:"+result.toString()+" , 用時:"+(end-start)/1000+"微秒");
long start2 = System.nanoTime();
List<List<Integer>> result2 = instance.levelOrderBottom2(t);
long end2 = System.nanoTime();
System.out.println("levelOrderBottom2---輸出:"+result2.toString()+" , 用時:"+(end2-start2)/1000+"微秒");
long start3 = System.nanoTime();
List<List<Integer>> result3 = instance.levelOrderBottom3(t);
long end3 = System.nanoTime();
System.out.println("levelOrderBottom3---輸出:"+result3.toString()+" , 用時:"+(end3-start3)/1000+"微秒");
long start4 = System.nanoTime();
List<List<Integer>> result4 = instance.levelOrderBottom4(t);
long end4 = System.nanoTime();
System.out.println("levelOrderBottom4---輸出:"+result4.toString()+" , 用時:"+(end4-start4)/1000+"微秒");
}
測試結果如下:
levelOrderBottom---輸出:[[8], [4, 5, 6, 7], [2, 3], [1]] , 用時:446微秒
levelOrderBottom2---輸出:[[8], [4, 5, 6, 7], [2, 3], [1]] , 用時:24微秒
levelOrderBottom3---輸出:[[8], [4, 5, 6, 7], [2, 3], [1]] , 用時:55微秒
levelOrderBottom4---輸出:[[8], [4, 5, 6, 7], [2, 3], [1]] , 用時:23微秒
因為只是採用單一資料測試,樣本數量過少,無法得出太過準確的結論,但還是可以明顯看出解法二和解法四其他條件一樣的情況下用時是最少的。
07 小結
以上就是全部內容,如果大家有什麼好的解法思路、建議或者其他問題,可以下方留言交流,點贊、留言、轉發就是對我最大的回報和支援!