從陣列形式建立一棵樹(用於leetcode測試)
這段時間時不時地會在leetcode上做些題,最近做到的大部分是與樹相關的題。由於是在本地的IDE上碼程式碼,如果每次測試都要到leetcode上來測的話,不僅要選中複製貼上一遍,而且每次測試還要讀一會條,一次兩次還好,次數多了還是比較煩。而如果在本地的類內的main()
函式裡測試的話,每次構建一個樹也比較麻煩,而且僅僅更換數值還好,若要更換樹的結構,那工程量就有點大了。
所以在這裡準備寫一個建立樹的函式來解決這個問題,後面做起題來效率也能高一些。
話不多說,下面先直接放上所用的程式碼:
本例中所構建的樹的結構 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1
package learning_java;
import LeetCode.TreeNode;
import java.util.Deque;
import java.util.LinkedList;
public class ConstructTree {
public static TreeNode constructTree(Integer[] nums){
if (nums.length == 0) return new TreeNode(0);
Deque<TreeNode> nodeQueue = new LinkedList <>();
// 建立一個根節點
TreeNode root = new TreeNode(nums[0]);
nodeQueue.offer(root);
TreeNode cur;
// 記錄當前行節點的數量(注意不一定是2的冪,而是上一行中非空節點的數量乘2)
int lineNodeNum = 2;
// 記錄當前行中數字在陣列中的開始位置
int startIndex = 1;
// 記錄陣列中剩餘的元素的數量
int restLength = nums.length - 1;
while(restLength > 0) {
// 只有最後一行可以不滿,其餘行必須是滿的
// // 若輸入的陣列的數量是錯誤的,直接跳出程式
// if (restLength < lineNodeNum) {
// System.out.println("Wrong Input!");
// return new TreeNode(0);
// }
for (int i = startIndex; i < startIndex + lineNodeNum; i = i + 2) {
// 說明已經將nums中的數字用完,此時應停止遍歷,並可以直接返回root
if (i == nums.length) return root;
cur = nodeQueue.poll();
if (nums[i] != null) {
cur.left = new TreeNode(nums[i]);
nodeQueue.offer(cur.left);
}
// 同上,說明已經將nums中的數字用完,此時應停止遍歷,並可以直接返回root
if (i + 1 == nums.length) return root;
if (nums[i + 1] != null) {
cur.right = new TreeNode(nums[i + 1]);
nodeQueue.offer(cur.right);
}
}
startIndex += lineNodeNum;
restLength -= lineNodeNum;
lineNodeNum = nodeQueue.size() * 2;
}
return root;
}
public static void main(String[] args) {
Integer[] nums = {5,4,8,11,null,13,4,7,2,null,null,null,1};
TreeNode root = ConstructTree.constructTree(nums);
System.out.println(root);
}
}
使用時,像上面main()
方法中一樣,只需要呼叫類內的靜態方法constructTree(Integer[] nums)
,輸入的參量為一個整型的陣列,陣列中的元素是按層次遍歷的二叉樹的值(若某節點在下一層中的某個兒子或兩個兒子為空,則在下一層的這一個或兩個位置填null
),與Leetcode中樹的表示方法相同,即可以直接把Leetcode上的測試用例按它的形式拖過來直接使用。
簡單解釋一下所用的方法,核心思想是在每一層,用一個佇列nodeQueue
來儲存該層的所有節點,然後用父節點的數量的兩倍來遍歷輸入的陣列(從上一層結束的地方開始),並從佇列中取出(位於上一層的)對應的父節點(此時已從佇列中刪去,因為用的方法為poll()
而不是peek()
),對於每一個值,建立相應的子節點連結到父節點,並加入到佇列中,依次不斷迴圈,直到遍歷完整個陣列。
這裡一個踩過的坑是,其中因為一部分數值為null
,而如果用int
基本型別的陣列的話,陣列內是不能用null
的,因此這裡用了int
的包裝類Integer
的陣列來作為傳入的引數的宣告。
最後,讓我們測試一下我們的程式碼,測試用的樹與上面一樣,測試中我們採用先序遍歷來進行輸出,檢視結果是否正確:
/*
用於測試的樹,與上例中相同
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
*/
package learning_java.sortTry;
import LeetCode.TreeNode;
import learning_java.ConstructTree;
public class ConstructTreeTest {
public void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
public static void main(String[] args) {
Integer[] nums = {5,4,8,11,null,13,4,7,2,null,null,null,1};
TreeNode root = ConstructTree.constructTree(nums);
new ConstructTreeTest().preOrder(root);
}
}
測試結果:
5 4 11 7 2 8 13 4 1
可以看到,我們得到了一棵所需要的樹。
更新
在實際使用中發現,leetcode中所給的case經常並不是標準的個數,最後一行往往是不滿的,如
5
/
4
這樣一棵樹,給出的陣列中僅有兩個數字:[5, 4]
,即最後一行中的末尾的null
會被捨棄,因此,在我們的程式中,應在遍歷過程中加入停止條件(更嚴謹的方式是儲存判定錯誤輸入的條件,並僅在樹的最後一行的遍歷過程中進行停止判定)。