Description
LeetCode的第590題與429、589題型類似,都爲樹(不一定是二叉樹)的各種形式的遍歷,因此放在一起總結。
對於上圖,要求求出前序遍歷、後序遍歷和層級遍歷的結果。
Example
前序遍歷結果:[1,3,5,6,2,4]
後序遍歷結果:[5,6,3,2,4,1]
層級遍歷結果:
[
[1],
[3,2,4],
[5,6]
]
Analysis
對於樹我們一般有兩種策略:
- 廣度優先搜索(BFS):從上到下一層一層的遍歷樹 ,也就是題目要求的層級遍歷。
- 深度優先搜索(DFS):從一個根節點開始,一直到達某個葉子節點,然後回到根節點到達另一個分支的葉子節點。根據根節點、左節點和右節點之間的相對順序,DFS策略可以進一步區分爲前序、中序和後序。
根據深度優先搜索與廣度優先搜索可以整理出下圖的四種情況:
Solution
對於第一題求前序遍歷,我們可以使用遞歸或者循環來完成,實際上這三道題都是如此。我們先看看遞歸版本:
public class Solution {
List<Integer> list;
public List<Integer> preorder(Node root) {
list = new ArrayList<>();
if(root == null) return list;
preorderCore(root);
return list;
}
private void preorderCore(Node root) {
if(root == null)
return;
list.add(root.val);
for(Node node : root.children)
preorderCore(node);
}
}
前序遍歷就是先將根節點放入結果列表中,然後再將左右子節點放入。遞歸的解法較爲簡單,下面看看循環的解法:
public class Solution2 {
public List<Integer> preorder(Node root) {
LinkedList<Integer> res = new LinkedList<>();
if(root == null)
return res;
Stack<Node> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
Node node = stack.pop();
res.add(node.val);
Collections.reverse(node.children);
for(Node children : node.children) {
stack.push(children);
}
}
return res;
}
}
在遞歸中我們使用棧來保存接下來要訪問的節點。首先我們將根節點壓入棧,棧中元素爲[1]
,然後我們將它彈出至結果列表並把它的子節點翻轉並放入棧,此時棧中元素爲[4, 2, 3]
;由於棧頂元素爲3
,因此將3
彈出至結果列表並把它的子節點翻轉並放入棧,此時棧中元素爲[4, 2, 6, 5]
;棧頂元素爲5
,因此將5
彈出至結果列表,5
沒有子節點,再把6
彈出至結果列表。如此反覆,我們便可以通過這種方式得到前序遍歷的結果列表[1, 3, 5, 6, 2, 4]
。
求後序遍歷與這題異曲同工,同樣先看看遞歸版本:
public class Solution {
List<Integer> list;
public List<Integer> postorder(Node root) {
list = new ArrayList<>();
if(root == null) return list;
postorderCore(root);
return list;
}
private void postorderCore(Node root) {
if(root == null)
return;
for(Node node : root.children)
postorderCore(node);
list.add(root.val);
}
}
我們僅僅將list.add(root.val);
這行代碼放到了遍歷子節點的for
語句之後,意味着先將所有子節點加入結果列表,最後再將根節點加入結果列表。下面是使用循環的解法:
public class Solution2 {
public List<Integer> postorder(Node root) {
LinkedList<Integer> res = new LinkedList<>();
if(root == null)
return res;
Stack<Node> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
Node node = stack.pop();
res.addFirst(node.val);
for(Node children : node.children) {
stack.push(children);
}
}
return res;
}
}
與前序遍歷不同的是我們不需要翻轉子節點列表,但是每次將結果添加到結果列表頭而不是尾。
第三題是層序遍歷(廣度優先搜索),不像上面兩題用遞歸實現更加簡單,我們通過循環來實現會更加簡潔明瞭,思路是使用一個隊列而非棧來保存每一層節點:
public class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()) {
int size = queue.size();
List<Integer> list = new ArrayList<>();
while(size-- != 0) {
Node node = queue.poll();
for(Node children : node.children) queue.add(children);
list.add(node.val);
}
res.add(list);
}
return res;
}
}