LeetCode-Tree-TraversalOfTree 二/N叉树遍历
二叉树遍历
1. 二叉树的前序遍历(144)
前序遍历:先遍历根节点、然后左节点、最后右节点。
1)递归
递归实现前序遍历一
class Solution {
public List<Integer> preorderTraversal(TreeNode root) { List<Integer> ans = new ArrayList<>(); preorderTraversalCore(ans, root); return ans; } void preorderTraversalCore(List<Integer> ans, TreeNode root){ if(root==null) return; ans.add(root.val); preorderTraversalCore(ans, root.left); preorderTraversalCore(ans, root.right); }
}
递归实现前序遍历二
class Solution {
List<Integer> ans = new ArrayList<>(); public List<Integer> preorderTraversal(TreeNode root) { if(root==null) return ans; ans.add(root.val); preorderTraversal(root.left); //不需要返回值 preorderTraversal(root.right); return ans; }
}
2)迭代
从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子节点压入栈中,先压右孩子再压左孩子。
栈实现迭代法前序遍历-思路一
class Solution {
public List<Integer> preorderTraversal(TreeNode root) { List<Integer> ans = new ArrayList<>(); if(root==null) return ans; Stack<TreeNode> stack = new Stack<TreeNode>(); stack.push(root); while(!stack.isEmpty()){ TreeNode t = stack.pop(); ans.add(t.val); if(t.right != null) stack.push(t.right); if(t.left != null) stack.push(t.left); } return ans; }
}
时间复杂度:访问每个节点恰好一次,时间复杂度为 O(N) ,其中 N 是节点的个数,也就是树的大小。
空间复杂度:取决于树的结构,最坏情况存储整棵树,因此空间复杂度是 O(N)。
一直遍历左节点至叶子节点,左节点遍历后再遍历右节点
栈实现迭代法前序遍历-思路二
class Solution {
public List<Integer> preorderTraversal(TreeNode root) { Deque<TreeNode> stack = new LinkedList<>(); TreeNode p = root; List<Integer> res = new ArrayList<>(); while (p != null || !stack.isEmpty()) { while (p != null) { res.add(p.val); stack.push(p); p = p.left; } p = stack.pop().right; } return res; }
}
3)莫里斯遍历
可以优化空间复杂度。算法不会使用额外空间,只需要保存最终的输出结果。如果实时输出结果,那么空间复杂度是 O(1)。参考
莫里斯前序遍历
class Solution {
public List
preorderTraversal(TreeNode root) {
LinkedListoutput = new LinkedList<>(); TreeNode node = root; while (node != null) { if (node.left == null) { output.add(node.val); node = node.right; } else { TreeNode predecessor = node.left; while ((predecessor.right != null) && (predecessor.right != node)) { predecessor = predecessor.right; } if (predecessor.right == null) { output.add(node.val); predecessor.right = node; node = node.left; } else{ predecessor.right = null; node = node.right; } } } return output;
}
}
时间复杂度:每个前驱恰好访问两次,因此复杂度是 O(N),其中 N 是顶点的个数,也就是树的大小。
空间复杂度:在计算中不需要额外空间,但是输出需要包含 N 个元素,因此空间复杂度为 O(N)。
2. 二叉树的中序遍历(94)
1)递归
中序遍历:左节点、根、右节点
递归实现中序遍历实现一
class Solution {
List<Integer> ans = new ArrayList<>(); //公共变量,不能放在函数inorderTraversal内 public List<Integer> inorderTraversal(TreeNode root) { if(root == null) return ans; inorderTraversal(root.left); ans.add(root.val); inorderTraversal(root.right); return ans; }
}
递归实现中序遍历实现二
class Solution {
public List <Integer> inorderTraversal(TreeNode root) { List <Integer> res = new ArrayList <> (); helper(root, res); return res; } public void helper(TreeNode root, List < Integer > res) { if (root == null) return; helper(root.left, res); res.add(root.val); helper(root.right, res); }
}
时间复杂度:O(n)。递归函数 T(n) = 2 * T(n/2)+1。
空间复杂度:最坏情况下需要空间 O(n),平均情况为 O(logn)。
2)迭代
该思路同前序迭代遍历思路二是一样的,遍历的过程一样,但是存储节点值的时机不同。
前序:在访问左节点的时候直接保存节点值。
中序:在节点从栈弹出的时候才会保存节点值。
画一棵树,模拟过程。
栈实现迭代法中序遍历-思路一
class Solution {
public List<Integer> inorderTraversal(TreeNode root) { List<Integer> ans = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode p = root; while(p!=null || !stack.isEmpty()){ while(p!=null){ stack.push(p); p = p.left; } TreeNode t = stack.pop(); ans.add(t.val); p = t.right; } return ans; }
}
时间复杂度:O(n)。
空间复杂度:O(n)。
3)莫里斯遍历
莫里斯中序遍历
class Solution {
public List < Integer > inorderTraversal(TreeNode root) { List < Integer > res = new ArrayList < > (); TreeNode curr = root; TreeNode pre; while (curr != null) { if (curr.left == null) { res.add(curr.val); curr = curr.right; // move to next right node } else { // has a left subtree pre = curr.left; while (pre.right != null) { // find rightmost pre = pre.right; } pre.right = curr; // put cur after the pre node TreeNode temp = curr; // store cur node curr = curr.left; // move cur to the top of the new tree temp.left = null; // original cur left be null, avoid infinite loops } } return res; }
}
3. 二叉树的后序遍历(145)
后序:左节点、右节点、根节点
1)递归
递归实现
class Solution {
List<Integer> ans = new ArrayList<>(); public List<Integer> postorderTraversal(TreeNode root) { if(root == null) return ans; postorderTraversal(root.left); postorderTraversal(root.right); ans.add(root.val); return ans; }
}
2)迭代
🍓
迭代实现一 peek(推荐)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) { List<Integer> ans = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; TreeNode last = null; while(cur!=null || !stack.isEmpty()){ while(cur != null){ stack.push(cur); cur = cur.left; } cur = stack.peek(); if(cur.right == null || cur.right==last){ ans.add(cur.val); stack.pop(); last = cur; cur = null; }else{ cur = cur.right; } } return ans; }
}
迭代实现二 pop
public List postorderTraversal(TreeNode root) {
if (root == null) return new ArrayList<Integer>();
TreeNode node = root;
List<Integer> ret = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
while(node != null || !stack.isEmpty()) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
// 后序遍历
if (node.right == null) {
ret.add(node.val);
node = null;
} else if (ret.size() == 0 || !ret.get(ret.size() - 1).equals(node.right.val)) {
stack.push(node);
node = node.right;
} else {
ret.add(node.val);
node = null;
}
}
return ret;
}
从根节点开始依次迭代,弹出栈顶元素输出到输出列表中,然后依次压入它的所有孩子节点,按照从上到下、从左至右的顺序依次压入栈中。
因为深度优先搜索后序遍历的顺序是从下到上、从左至右,所以需要将输出列表逆序输出。
前序遍历:先压入右节点、在压入左节点。
后序遍历:先压入左节点、在压入右节点,最后对输出的结果逆序。(或一直像头部插入元素、而不是尾部插入元素)
这个题解只是能返回遍历的结果,并不是严格意义上树拓扑结构的遍历。虽然结果是正确,但是如果需要按照后续遍历的顺序对树节点进行访问(或操作),此解法就无法满足。
迭代
class Solution {
public List<Integer> postorderTraversal(TreeNode root) { List<Integer> ans = new ArrayList(); if(root==null) return ans; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode t = stack.pop(); ans.add(0, t.val); if(t.left != null) stack.push(t.left); if(t.right != null) stack.push(t.right); } return ans; }
}
时间复杂度:访问每个节点恰好一次,因此时间复杂度为 O(N),其中 N 是节点的个数,也就是树的大小。
空间复杂度:取决于树的结构,最坏情况需要保存整棵树,因此空间复杂度为 O(N)。
二叉树前中后序迭代遍历总结
前中后序迭代遍历总结
public List
traversal(TreeNode root) { if (root == null) return new ArrayList<Integer>(); TreeNode node = root; List<Integer> ret = new ArrayList<Integer>(); Stack<TreeNode> stack = new Stack<TreeNode>(); while(node != null || !stack.isEmpty()) { while (node != null) { stack.push(node); // 先序遍历 node = node.left; } node = stack.pop(); // 中序遍历 node = node.right; // 后序遍历 } return ret;
}
4. 二叉树的层序遍历(102)
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
Related Topics 树 广度优先搜索
使用队列,首先根元素入队,当队列不为空的时候求当前队列的长度 s_i,依次从队列中取 s_i个元素进行拓展(将左右节点分别加入到队列中),然后进入下一次迭代。
二叉树的层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ans = new ArrayList<>(); if(root==null) return ans; Queue<TreeNode> queue = new LinkedList<>(); TreeNode t = root; queue.offer(t); while(queue.size()!=0){ int size = queue.size(); List<Integer> level = new ArrayList<>(); for(int i=0; i<size; i++){ t = queue.poll(); level.add(t.val); if(t.left!=null) queue.offer(t.left); if(t.right!=null) queue.offer(t.right); } ans.add(level); } return ans; }
}
5. 二叉树的层次遍历 II(107)
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其自底向上的层次遍历为:
[
[15,7],
[9,20],
[3]
]
Related Topics 树 广度优先搜索
1)迭代
同上一提思路一样,结果倒叙输出即可
队列实现-倒叙输出
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> ans = new ArrayList<>(); if(root==null) return ans; Queue<TreeNode> queue = new LinkedList<>(); TreeNode t = root; queue.offer(t); while(queue.size()!=0){ int size = queue.size(); List<Integer> level = new ArrayList<>(); for(int i=0; i<size; i++){ t = queue.poll(); level.add(t.val); if(t.left!=null) queue.offer(t.left); if(t.right!=null) queue.offer(t.right); } ans.add(0, level); //不能使用addAll函数!倒叙插入 } return ans; }
}
2)递归
🍓 多使用一个形参,记录当前层号。
如果子列表的数量小于层数,说明第一次到达该层,为结果添加一个子列表。注意为了实现自底向上输出,每次都将子列表添加到结果的开头。
子列表的下标对应的是自底向上遍历时结点所在的层号,即 0 对应最后一层。
然后对遍历到的顶点,将其添加到所在层号对应的子列表中即可。
递归实现
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); find(root, 1, result); return result; } private void find(TreeNode root, int level, List<List<Integer>> result) { if (root == null) { return; } if (level > result.size()) { result.add(0, new ArrayList<>()); //向头部插入! } result.get(result.size() - level).add(root.val); find(root.left, level + 1, result); find(root.right, level + 1, result); }
}
6. 二叉树的锯齿形层次遍历(103)
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回锯齿形层次遍历如下:
[
[3],
[20,9],
[15,7]
]
Related Topics 栈 树 广度优先搜索
1)队列+奇偶层判断
Java LinkedList add 是加在list尾部。LinkedList push 施加在list头部,等同于addFirst。
队列+奇偶层add/push
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) { List<List<Integer>> ans = new ArrayList<>(); if(root == null) return ans; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; queue.offer(root); while(!queue.isEmpty()){ int size = queue.size(); LinkedList<Integer> item = new LinkedList(); for(int i=0;i<size;i++){ TreeNode t = queue.poll(); if((level & 1)==0){ item.add(t.val); }else{ item.push(t.val); } if(t.left!=null){ queue.offer(t.left); } if(t.right!=null){ queue.offer(t.right); } } ans.add(item); level++; } return ans; }
}
也可以使用 LinkedList 的 addFirst 与 addLast 方法。
2)递归方法
递归地遍历每个结点的左子结点和右子结点,同时记录每个结点的层号,通过层号的奇偶判断从左到右还是从右到左。
若为从左到右,每次都添加到该层list的尾部。
若为从右到左,每次都添加到该层list的首部。
递归方法
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) { List<List<Integer>> res = new LinkedList<>(); dfs(root, 1, res); return res; } private void dfs(TreeNode r, int layer, List<List<Integer>> res) { if(r == null) return; if(layer > res.size())//说明第一次到达该层 res.add(new LinkedList<Integer>()); if(layer % 2 == 0) res.get(layer - 1).add(0, r.val); else res.get(layer - 1).add(r.val); dfs(r.left, layer + 1, res); dfs(r.right, layer + 1, res); }
}
7. 二叉树的垂序遍历(987)
给定二叉树,按垂序遍历返回其结点值。
对位于 (X, Y) 的每个结点而言,其左右子结点分别位于 (X-1, Y-1) 和 (X+1, Y-1)。
把一条垂线从 X = -infinity 移动到 X = +infinity ,每当该垂线与结点接触时,我们按从上到下的顺序报告结点的值( Y 坐标递减)。
如果两个结点位置相同,则首先报告的结点值较小。
按 X 坐标顺序返回非空报告的列表。每个报告都有一个结点值列表。
提示:
树的结点数介于 1 和 1000 之间。
每个结点值介于 0 和 1000 之间。
输入:[3,9,20,null,null,15,7]
输出:[[9],[3,15],[20],[7]]
解释:
在不丧失其普遍性的情况下,我们可以假设根结点位于 (0, 0):
然后,值为 9 的结点出现在 (-1, -1);
值为 3 和 15 的两个结点分别出现在 (0, 0) 和 (0, -2);
值为 20 的结点出现在 (1, -1);
值为 7 的结点出现在 (2, -2)。
输入:[1,2,3,4,5,6,7]
输出:[[4],[2],[1,5,6],[3],[7]]
解释:
根据给定的方案,值为 5 和 6 的两个结点出现在同一位置。
然而,在报告 "[1,5,6]" 中,结点值 5 排在前面,因为 5 小于 6。
记录坐标
1)使用深度优先搜索找到每个节点的坐标(创建一个结构,同时存储节点值与节点坐标)。保持当前节点 (x, y) 移动的过程中,坐标变化为 (x-1, y+1) 或 (x+1, y+1) 取决于是左孩子还是右孩子。
2)通过 x 坐标排序,再根据 y 坐标排序,这样确保以正确的顺序添加到答案中。
记录坐标
class Solution {
List<Location> locations;
public List<List<Integer>> verticalTraversal(TreeNode root) {
locations = new ArrayList<>();
dfs(root, 0, 0);
Collections.sort(locations); //!!!
List<List<Integer>> ans = new ArrayList<>();
ans.add(new ArrayList<Integer>());
int pre = locations.get(0).x;
for(Location t : locations){
if(pre != t.x){
pre = t.x;
ans.add(new ArrayList<Integer>());
}
ans.get(ans.size() - 1).add(t.val);
}
return ans;
}
public void dfs(TreeNode root, int x, int y){
if(root!=null){
locations.add(new Location(x, y, root.val));
dfs(root.left, x-1, y+1); //!!!
dfs(root.right, x+1, y+1);
}
}
}
class Location implements Comparable{
int x,y,val;
Location(int x, int y, int val){
this.x = x;
this.y = y;
this.val = val;
}
@Override
public int compareTo(Location that){
if(this.x != that.x){
return Integer.compare(this.x, that.x);
}else if(this.y != that.y){
return Integer.compare(this.y, that.y);
}else{
return Integer.compare(this.val, that.val);
}
}
}
注:存储坐标时,子节点对y+1,与题目y-1(从上到下,y递减)不同,是因为程序中判断y是递增的。
N叉树
N叉树的层序遍历(429)
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
返回其层序遍历:
[
[1],
[3,2,4],
[5,6]
]
说明:
树的深度不会超过 1000。
树的节点总数不会超过 5000。
Related Topics 树 广度优先搜索
N 叉树的层序遍历-队列
class Solution {
public List<List<Integer>> levelOrder(Node root) { List<List<Integer>> ans = new ArrayList<>(); if(root == null) return ans; // Queue<Node> queue = new Queue<>(); Queue<Node> queue = new LinkedList<>(); queue.offer(root); while(!queue.isEmpty()){ int size = queue.size(); List<Integer> level = new ArrayList<>(); for(int i=0; i<size; i++){ Node t = queue.poll(); level.add(t.val); /*if(t.children != null){ for(Node per: t.children){ queue.offer(per); } }*/ queue.addAll(t.children); } ans.add(level); } return ans; }
}
N 叉树的层序遍历-递归
class Solution {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrder(Node root) {
if (root != null) traverseNode(root, 0);
return result;
}
private void traverseNode(Node node, int level) {
if (result.size() <= level) {
result.add(new ArrayList<>());
}
result.get(level).add(node.val);
for (Node child : node.children) {
traverseNode(child, level + 1);
}
}
}
N叉树的前序遍历(589)
给定一个 N 叉树,返回其节点值的前序遍历。
例如,给定一个 3叉树 :
返回其前序遍历: [1,3,5,6,2,4]。
说明: 递归法很简单,你可以使用迭代法完成此题吗? Related Topics 树
N 叉树的前序遍历
代码
N 叉树的前序遍历
代码
N叉树的后序遍历(590)
展示代码
代码