做題目前隨便說點
-
樹是一種抽象數據類型,一種具有樹結構形式的數據集合。
-
節點個數確定,有層次關係。
-
有根節點。
-
除了根,每個節點有且只有一個父節點。
-
沒有環路。
-
所有數據結構都可以用鏈表表示或者用數組表示,樹也一樣。
32 - II. 從上到下打印二叉樹 II
從上到下按層打印二叉樹,同一層的節點按從左到右的順序打印,每一層打印到一行。
例如:
給定二叉樹: [3,9,20,null,null,15,7],3
/
9 20
/
15 7
返回其層次遍歷結果:[
[3],
[9,20],
[15,7]
]提示:
節點總數 <= 1000
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
解題:
審題:
- 二叉樹的廣度遍歷普遍使用隊列Queue來實現。
- 爲什麼二叉樹的廣度遍歷使用Queue呢?這個很難解釋。只能說,把樹看成一個金字塔結構的社羣。假定同一階級的節點越靠左邊表示權位越大,那麼他們的子嗣的權位也會按照父輩的順序排列。而這種排位恰好形成了一個隊列,越早進入隊列的節點,越先訪問他們的子節點,他們的子節點就越早進入隊列。只能說是巧合。
- 如果只是簡單的廣度遍歷他的代碼如下:
class Solution {
public List<Integer> levelOrder(TreeNode root) {
// 用於存放結果
List<Integer> list = new ArrayList();
// 輔助隊列
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) {
return list;
}
// 根節點進入丟列
queue.offer(root);
while(!queue.isEmpty()) {
// 父節點退出隊列,訪問父節點
TreeNode node = queue.poll();
list.add(node);
// 父節點的大孩子進入隊列
if (node.left != null) {
queue.offer(node.left);
}
// 父節點的小孩子進入隊列
if (node.right != null) {
queue.offer(node.right);
}
// 下一個權位的進入隊列(下一個權位的人,可能是同級別低權人,或者是下級別的高權人)
}
return list;
}
}
- 說實在的上面的代碼我依然看得不大懂,或者可以換一種思路。
- 我們可以理解所謂的廣度優先遍歷,就是一層一層的遍歷樹(我們把深度一樣的節點認爲是同一層)。
- 然後我們發現層與層之間存在的聯繫就是,下層的節點都是上一層節點的子節點。或者準確的說,下層的節點,可以分成N組,每一個組都和上一層節點的映射。
- 既然這樣,我們只要保存上一層節點到數組中,然後通過遍歷數組,對數組的每個元素進行映射,將映射結果輸出到另一個數組。
- 重複上面的過程,知道不再有子節點可以映射。
- 最初的節點就是根節點,映射後子節點放到另一個數組,然後繼續將兩個映射成4個(如果是二叉樹的話)。重複這個過程。
- 而其中還有一個問題就是,按照我們這個思路就會創建很多個數組了。這對內存開銷很大。
- 面對這個問題,很好處理,就是思考是否能複用數組。
- 複用數據的組話,得保證不能覆蓋違背消費的數據。
- 也就是說我們需要對數據的每個元素加一個狀態量
已經消費的元素就打上允許覆蓋的標記。 - 其次就是數組是順序消費的。從左往右,從上往下。不管怎麼說,在一維數組看來就是順序消費,從左往右。
- 而加入數組的順序也是按照順序的。
- 結合上面的兩個思路,我們可以確定,用隊列比數組好。其實用數組也可以,只是我們需要模擬隊列的offer和poll實現。
- 至於怎麼複用呢?也很好處理,把映射後的節點拼接在原先輸入的隊列尾部。
- 咋一看,拼接後還是一個隊列。所以說,我們壓根就不需要開閉很多存儲空間。一個隊列就夠了。
- 業務邏輯思路就還是剛剛那個,也就說我們需要實現一個映射函數。
代碼如下。
代碼如下:
class Solution {
// map映射函數,負責將上層節點映射成下層節點。
void Queue<Node> map(Queue<Node> input) {
// 記錄一下輸入數組的長度,免得把輸出結果也消費了。
int len = input.size();
// output和input其實是一個隊列來的
Queue<Node> output = input;
for(int i; i < len; i++) {
Node father = input.poll();
// 打印輸入的節點內容
print(father.val);
for(Node son : father.chilren) {
output.offer(son);
}
}
return output;
}
void levelOrder(TreeNode root) {
// 我們只需要用迭代法,不停的將輸出結果作爲輸入反覆調用就可以按照廣度遍歷順序打印所有N叉樹節點了。
Queue<Node> input = new Queue();
input.offer(root);
while(!output.isEmpty()) {
input = output;
output = map( input );
}
}
}
開始解題:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
// 用於存放結果
List<List<Integer>> list = new ArrayList(); // 用於存放同一層的節點
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) {
return list;
}
queue.offer(root);
while(!queue.isEmpty()) {
List<Integer> tmp = new ArrayList();
int len = queue.size();
// 相當於前面的map函數
for (int i = 0; i < len; i ++) {
TreeNode node = queue.poll();
tmp.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
list.add(tmp);
}
return list;
}
}
- 其實這個寫法沒有上面N叉樹的map()函數的寫法好看,但是大部分都喜歡寫成一坨。
總結:
- 我們要找到問題的本質,以及看到問題的角度。我們廣度遍歷不能以節點爲單位來思考問題,而是以層爲單位來思考問題。
- 其次就是不要看到答案就覺得已經明白了原理。
- 很多時候我們看到算法答案都會察覺其不友好的地方,因爲代碼是給計算機看的。我們需要靜下心來不能浮躁。理清其面向對象的,對人友好的解題思路,從而挖掘問題的本質。