🌟欢迎来到 我的博客 —— 探索技术的无限可能!
12.二叉树的后序遍历
题目链接:145. 二叉树的后序遍历
给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
树中节点的数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶:递归算法很简单,你可以通过迭代算法完成吗?
题解:
方法一:递归遍历
先递归左子树,接着递归右子树,再访问根节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
import java.util.*;
class Solution {
private List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表
/**
* 执行后序遍历,返回遍历结果列表。
* @param root 二叉树的根节点
* @return 后序遍历结果列表
*/
public List<Integer> postorderTraversal(TreeNode root) {
dfs(root); // 调用深度优先搜索函数进行后序遍历
return ans; // 返回后序遍历结果列表
}
/**
* 深度优先搜索函数,递归遍历二叉树,并将节点值存入结果列表。
* @param root 当前处理的节点
*/
private void dfs(TreeNode root) {
if (root == null) { // 递归终止条件:当前节点为空
return;
}
dfs(root.left); // 递归遍历左子树
dfs(root.right); // 递归遍历右子树
ans.add(root.val); // 将当前节点值加入结果列表(在递归后面加入)
}
}
方法二:栈实现非递归遍历
在这个后序遍历的实现中,使用了一个额外的辅助栈和一个变量 prev
来记录上一个访问过的节点。算法的核心思想是在访问每个节点时,先判断其右子节点是否被访问过,如果右子节点未被访问过,则先遍历右子树;如果右子节点已被访问过或不存在,则将当前节点的值加入结果列表,并将其弹出栈,并更新 prev
为当前节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
import java.util.*;
class Solution {
/**
* 执行后序遍历,返回遍历结果列表。
* @param root 二叉树的根节点
* @return 后序遍历结果列表
*/
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表
Deque<TreeNode> stk = new ArrayDeque<>(); // 辅助栈,用于遍历过程中保存节点
TreeNode prev = null; // 用于记录上一个访问过的节点
// 遍历二叉树
while (root != null || !stk.isEmpty()) {
if (root != null) {
stk.push(root); // 将当前节点入栈
root = root.left; // 移动到左子节点
} else {
TreeNode peekNode = stk.peek(); // 查看栈顶节点,但不出栈
if (peekNode.right != null && peekNode.right != prev) { // 如果栈顶节点的右子节点存在且未被访问过
root = peekNode.right; // 移动到右子节点
} else { // 如果栈顶节点的右子节点不存在或已被访问过
stk.pop(); // 栈顶节点出栈
ans.add(peekNode.val); // 将栈顶节点的值加入结果列表
prev = peekNode; // 更新上一个访问过的节点
}
}
}
return ans; // 返回后序遍历结果列表
}
}
方法三:Morris 实现中序遍历
这段代码使用了 Morris 遍历的思想实现了二叉树的后序遍历,其时间复杂度为 O(n),其中 n 是二叉树中的节点数。Morris 遍历算法的优点是空间复杂度为 O(1),不需要额外的栈空间。在后序遍历中,需要额外的操作来将遍历路径上的节点逆序添加到结果列表中,这部分操作在 reverseAddNodes
方法中实现。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
import java.util.*;
class Solution {
/**
* 执行 Morris 实现的后序遍历,返回遍历结果列表。
* @param root 二叉树的根节点
* @return 后序遍历结果列表
*/
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表
TreeNode dummy = new TreeNode(-1); // 添加一个虚拟节点作为辅助节点
dummy.left = root; // 将虚拟节点的左子节点指向根节点
TreeNode cur = dummy; // 初始化当前节点为虚拟节点
// 遍历二叉树
while (cur != null) {
if (cur.left == null) { // 如果当前节点的左子节点为空
cur = cur.right; // 移动到当前节点的右子节点
} else { // 如果当前节点的左子节点不为空
TreeNode prev = cur.left; // 找到当前节点的左子树的最右下角节点
while (prev.right != null && prev.right != cur) {
prev = prev.right;
}
if (prev.right == null) { // 如果最右下角节点的右子节点为空
prev.right = cur; // 将最右下角节点的右子节点指向当前节点
cur = cur.left; // 移动到当前节点的左子节点
} else { // 如果最右下角节点的右子节点不为空
reverseAddNodes(cur.left, prev, ans); // 将从当前节点的左子节点到 prev 节点这段路径上的节点,按照逆序添加到结果列表中
prev.right = null; // 恢复最右下角节点的右子节点为null
cur = cur.right; // 移动到当前节点的右子节点
}
}
}
return ans; // 返回后序遍历结果列表
}
/**
* 将从 start 节点到 end 节点这段路径上的节点,按照逆序添加到结果列表中。
* @param start 起始节点
* @param end 终止节点
* @param ans 结果列表
*/
private void reverseAddNodes(TreeNode start, TreeNode end, List<Integer> ans) {
reverseNodes(start, end); // 先翻转从 start 节点到 end 节点的路径上的节点
TreeNode cur = end; // 初始化当前节点为 end 节点
while (true) {
ans.add(cur.val); // 将当前节点的值加入结果列表
if (cur == start) { // 如果当前节点等于起始节点,结束循环
break;
}
cur = cur.right; // 移动到当前节点的右子节点
}
reverseNodes(end, start); // 再次翻转从 start 节点到 end 节点的路径上的节点,恢复原状
}
/**
* 翻转从 start 节点到 end 节点这段路径上的节点。
* @param start 起始节点
* @param end 终止节点
*/
private void reverseNodes(TreeNode start, TreeNode end) {
if (start == end) { // 如果起始节点等于终止节点,不需要翻转,直接返回
return;
}
TreeNode x = start; // 初始化 x 节点为起始节点
TreeNode y = start.right; // 初始化 y 节点为起始节点的右子节点
while (true) {
TreeNode z = y.right; // 初始化 z 节点为 y 节点的右子节点
y.right = x; // 将 y 节点的右子节点指向 x 节点
x = y; // 更新 x 节点为 y 节点
y = z; // 更新 y 节点为 z 节点
if (x == end) { // 如果 x 节点等于终止节点,结束循环
break;
}
}
}
}
13.二叉树的垂序遍历
题目链接:987. 二叉树的垂序遍历
给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列。
对位于 (row, col) 的每个结点而言,其左右子结点分别位于 (row + 1, col - 1) 和 (row + 1, col + 1) 。树的根结点位于 (0, 0) 。
二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有结点,形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个结点,则按结点的值从小到大进行排序。
返回二叉树的 垂序遍历 序列。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[9],[3,15],[20],[7]]
解释:
列 -1 :只有结点 9 在此列中。
列 0 :只有结点 3 和 15 在此列中,按从上到下顺序。
列 1 :只有结点 20 在此列中。
列 2 :只有结点 7 在此列中。
示例 2:
输入:root = [1,2,3,4,5,6,7]
输出:[[4],[2],[1,5,6],[3],[7]]
解释:
列 -2 :只有结点 4 在此列中。
列 -1 :只有结点 2 在此列中。
列 0 :结点 1 、5 和 6 都在此列中。
1 在上面,所以它出现在前面。
5 和 6 位置都是 (2, 0) ,所以按值从小到大排序,5 在 6 的前面。列 1 :只有结点 3 在此列中。
列 2 :只有结点 7 在此列中。
示例 3:
输入:root = [1,2,3,4,6,5,7]
输出:[[4],[2],[1,5,6],[3],[7]]
解释: 这个示例实际上与示例 2 完全相同,只是结点 5 和 6 在树中的位置发生了交换。
因为 5 和 6 的位置仍然相同,所以答案保持不变,仍然按值从小到大排序。
提示:
树中结点数目总数在范围 [1, 1000] 内
0 <= Node.val <= 1000
题解:
方法:深度优先DFS
这段代码实现了二叉树的垂直遍历,将节点按照列号分组存储,并在每组中按照行号和节点值进行排序。整体思路是使用深度优先搜索遍历二叉树,在遍历的过程中记录每个节点的行号、列号和值,并将其分组存储在 groups
中。最后,对每个分组按照题目要求的顺序进行排序,并构造出最终的结果列表。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
/**
* 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
* @param root 二叉树的根节点
* @return 垂直遍历结果列表
*/
public List<List<Integer>> verticalTraversal(TreeNode root) {
// 使用 TreeMap 来保存分组,以确保按列顺序遍历
Map<Integer, List<int[]>> groups = new TreeMap<>();
// 执行深度优先搜索,记录节点的行号、列号和值
dfs(root, 0, 0, groups);
// 构造结果列表
List<List<Integer>> ans = new ArrayList<>(groups.size());
for (List<int[]> g : groups.values()) {
// 对于同一列的节点,按照行号和值进行排序
g.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
List<Integer> vals = new ArrayList<>(g.size());
// 将排序后的节点值加入结果列表
for (int[] p : g) {
vals.add(p[1]);
}
ans.add(vals);
}
return ans;
}
/**
* 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
* @param node 当前处理的节点
* @param row 当前节点的行号
* @param col 当前节点的列号
* @param groups 保存分组结果的映射表
*/
private void dfs(TreeNode node, int row, int col, Map<Integer, List<int[]>> groups) {
if (node == null) {
return;
}
// 将当前节点加入对应列的分组中
groups.computeIfAbsent(col, k -> new ArrayList<>()).add(new int[]{row, node.val});
// 递归处理左右子节点,更新行号和列号
dfs(node.left, row + 1, col - 1, groups);
dfs(node.right, row + 1, col + 1, groups);
}
}
方法二:在 DFS 的同时记录 col的最小值,这样无需对 key 排序,也无需使用有序字典。
使用了 minCol
变量来记录最小的列号,而不是直接从 0 开始遍历 groups
映射表。这个变量的作用是确保我们的列号是从最小列号开始的,这样我们可以保证结果列表中的列是按照从左到右的顺序排列的。
具体区别在于以下几点:
-
使用
minCol
变量:我们初始化minCol
为 0,并在遍历节点的过程中更新它。这样可以确保我们的列号是从最小列号开始的,而不是直接从 0 开始。这样做的好处是确保结果列表中的列是按照从左到右的顺序排列的。 -
构造结果列表时的列号遍历:在构造结果列表时,我们使用
minCol
变量作为起始列号,并以minCol + groups.size()
作为结束列号。这样可以确保我们遍历到了所有的列号,并保证了结果列表中的列是按照从左到右的顺序排列的。
class Solution {
private int minCol; // 用于记录最小列号
/**
* 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
* @param root 二叉树的根节点
* @return 垂直遍历结果列表
*/
public List<List<Integer>> verticalTraversal(TreeNode root) {
Map<Integer, List<int[]>> groups = new HashMap<>(); // 用于保存分组结果的映射表
dfs(root, 0, 0, groups); // 执行深度优先搜索遍历二叉树
List<List<Integer>> ans = new ArrayList<>(groups.size());
// 遍历每个分组,按照列号顺序构造结果列表
for (int col = minCol; col < minCol + groups.size(); col++) {
List<int[]> g = groups.get(col);
g.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]); // 对每个分组进行排序
List<Integer> vals = new ArrayList<>(g.size());
// 将排序后的节点值加入结果列表
for (int[] p : g) {
vals.add(p[1]);
}
ans.add(vals);
}
return ans; // 返回垂直遍历结果列表
}
/**
* 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
* @param node 当前处理的节点
* @param row 当前节点的行号
* @param col 当前节点的列号
* @param groups 保存分组结果的映射表
*/
private void dfs(TreeNode node, int row, int col, Map<Integer, List<int[]>> groups) {
if (node == null) {
return;
}
minCol = Math.min(minCol, col); // 更新最小列号
groups.computeIfAbsent(col, k -> new ArrayList<>()).add(new int[]{row, node.val}); // 将当前节点加入对应列的分组中
// 递归处理左右子节点,更新行号和列号
dfs(node.left, row + 1, col - 1, groups);
dfs(node.right, row + 1, col + 1, groups);
}
}
方法三:双数组
这段代码实现了二叉树的垂直遍历,并将节点按照列号分组存储在 left
和 right
中,一个列表 left
负责统计负数 col
,另一个列表 right
负责统计非负数 col
。
然后将分组中的节点按照行号和节点值排序,最后将排序后的节点值添加到结果列表中。
整体思路是使用深度优先搜索遍历二叉树,在遍历的过程中将节点分组存储,并按照要求进行排序和组装结果列表。
相当于用两个列表来模拟双端队列。
class Solution {
/**
* 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
* @param root 二叉树的根节点
* @return 垂直遍历结果列表
*/
public List<List<Integer>> verticalTraversal(TreeNode root) {
List<List<int[]>> left = new ArrayList<>(); // 左侧分组列表
List<List<int[]>> right = new ArrayList<>(); // 右侧分组列表
dfs(root, 0, 0, left, right); // 执行深度优先搜索遍历二叉树,生成左右分组列表
List<List<Integer>> ans = new ArrayList<>(left.size() + right.size()); // 结果列表
Collections.reverse(left); // 反转左侧分组列表,使列号从小到大排序
add(ans, left); // 添加左侧分组到结果列表
add(ans, right); // 添加右侧分组到结果列表
return ans; // 返回垂直遍历结果列表
}
/**
* 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
* @param node 当前处理的节点
* @param row 当前节点的行号
* @param col 当前节点的列号
* @param left 左侧分组列表
* @param right 右侧分组列表
*/
private void dfs(TreeNode node, int row, int col, List<List<int[]>> left, List<List<int[]>> right) {
if (node == null) {
return;
}
// 根据节点的列号确定分组,将节点添加到对应的分组中
if (col < -left.size()) {
left.add(new ArrayList<>());
} else if (col == right.size()) {
right.add(new ArrayList<>());
}
(col < 0 ? left.get(-col - 1) : right.get(col)).add(new int[]{row, node.val});
// 递归处理左右子节点,更新行号和列号
dfs(node.left, row + 1, col - 1, left, right);
dfs(node.right, row + 1, col + 1, left, right);
}
/**
* 将分组列表中的节点值添加到结果列表中。
* @param ans 结果列表
* @param a 分组列表
*/
private void add(List<List<Integer>> ans, List<List<int[]>> a) {
for (List<int[]> g : a) {
Collections.sort(g, (p, q) -> p[0] != q[0] ? p[0] - q[0] : p[1] - q[1]); // 对分组中的节点按行号和值进行排序
List<Integer> vals = new ArrayList<>(g.size());
for (int[] p : g) {
vals.add(p[1]); // 将排序后的节点值添加到结果列表
}
ans.add(vals); // 将结果列表加入到结果列表中
}
}
}
方法四:单数组
把所有 (col,row,val) 全部丢到同一个列表中,排序后按照 col分组。
class Solution {
public List<List<Integer>> verticalTraversal(TreeNode root) {
List<int[]> data = new ArrayList<>();
dfs(root, 0, 0, data);
List<List<Integer>> ans = new ArrayList<>();
data.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] != b[1] ? a[1] - b[1] : a[2] - b[2]);
int lastCol = Integer.MIN_VALUE;
for (int[] d : data) {
if (d[0] != lastCol) {
lastCol = d[0];
ans.add(new ArrayList<>());
}
ans.get(ans.size() - 1).add(d[2]);
}
return ans;
}
private void dfs(TreeNode node, int row, int col, List<int[]> data) {
if (node == null) {
return;
}
data.add(new int[]{col, row, node.val});
dfs(node.left, row + 1, col - 1, data);
dfs(node.right, row + 1, col + 1, data);
}
}
14.二叉树的层序遍历
题目链接:102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
题解:
方法:迭代
广度优先遍历是按层层推进的方式,遍历每一层的节点。题目要求的是返回每一层的节点值,所以这题用广度优先来做非常合适。
广度优先需要用队列作为辅助结构,我们先将根节点放到队列中,然后不断遍历队列。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
import java.util.*;
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> res = new ArrayList<List<Integer>>();
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
//将根节点放入队列中,然后不断遍历队列
queue.add(root);
while(queue.size()>0) {
//获取当前队列的长度,这个长度相当于 当前这一层的节点个数
int size = queue.size();
ArrayList<Integer> tmp = new ArrayList<Integer>();
//将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
//如果节点的左/右子树不为空,也放入队列中
for(int i=0;i<size;++i) {
TreeNode t = queue.remove();
tmp.add(t.val);
if(t.left!=null) {
queue.add(t.left);
}
if(t.right!=null) {
queue.add(t.right);
}
}
//将临时list加入最终返回结果中
res.add(tmp);
}
return res;
}
}
方法:递归
import java.util.*;
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
//用来存放最终结果
List<List<Integer>> res = new ArrayList<List<Integer>>();
dfs(1,root,res);
return res;
}
void dfs(int index,TreeNode root, List<List<Integer>> res) {
//假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
if(res.size()<index) {
res.add(new ArrayList<Integer>());
}
//将当前节点的值加入到res中,index代表当前层,假设index是3,节点值是99
//res是[ [1],[2,3] [4] ],加入后res就变为 [ [1],[2,3] [4,99] ]
res.get(index-1).add(root.val);
//递归的处理左子树,右子树,同时将层数index+1
if(root.left!=null) {
dfs(index+1, root.left, res);
}
if(root.right!=null) {
dfs(index+1, root.right, res);
}
}
}
15.二叉树的层序遍历 II
题目链接:107. 二叉树的层序遍历 II
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000
题解:
方法:BFS
BFS遍历比较直观,就是按层遍历
我们将我们一层遍历得到的结果放到一个数组中,等这一层遍历完后,将这个数组放入到最终结果集res中。
因为题目要求是反序输出,所以我们只需将res翻转一下就可以了。
如上图,首先创建一个队列,将根节点root放入队列中,然后不断循环遍历这个队列。
对于树的第一层,其长度为1,而此时根节点也放到队列中了,所以队列的长度也是1,我们取出一个元素,将其放入临时数组中。
如果这个节点的左右孩子不为空,则将他们也放入队列中。
上图是处理第二层的过程,类似于第一层,由于第一层遍历完后,队列里有两个元素了,所以第二次遍历的时候,可以确定队列长度是2,于是执行两次,将2和3取出,放入临时数组中,之后再将2和3的两个孩子也放入队列中。
处理第三层时,队列长度就是4了,于是取出4个元素,再次处理。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
//用来存放最终结果
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
//创建一个队列,将根节点放入其中
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while(!queue.isEmpty()) {
//每次遍历的数量为队列的长度
int size = queue.size();
ArrayList<Integer> tmp = new ArrayList<Integer>();
//将这一层的元素全部取出,放入到临时数组中,如果节点的左右孩子不为空,继续放入队列
for(int i=0;i<size;++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);
}
}
res.add(tmp);
}
//翻转最终结果并返回
Collections.reverse(res);
return res;
}
}
方法:BFS 2
第一种方式中,我们将最终结果集定义成数组,等所有元素都放置完后,再翻转一下数组。
我们可以将其结构改成链表,以后每层遍历完将结果放入到链表的第一位,这样就自动完成了翻转的功能了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
//改用链表实现,每次都插入到第一位
LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while(!queue.isEmpty()) {
int size = queue.size();
ArrayList<Integer> tmp = new ArrayList<Integer>();
for(int i=0;i<size;++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);
}
}
//每次都保存到链表的第一位,这样自带了翻转的功能
res.add(0,tmp);
}
return res;
}
}
方法:DFS
DFS实现就不那么直观了,我们可以把二叉树结构改变一下,想象成一个三角形的结构
我们在递归遍历时,将第一层的元素放入到第一层的数组中,将第二层的的元素放入到第二层,第三层的元素放入第三层。
第三层的4是在5之前被遍历的,所以放入到第三层数组时,4就在5前面,同理6是在7前面被遍历的,所以放入到第三层时6就在7前面。
这次我们不用创建队列了,直接在最终结果集res上操作,res是个二维数组,集合中嵌套集合,但现在没有任何嵌套的集合。
如上图所示,一开始res是空的,当遍历到第一层时候,我们创建一个集合,放入res,然后将第一层的1,放到res[0]中。
遍历到第二层时,再创建一个集合放入res,然后将2放入res[1]中。
遍历到第三层时,同样再创建一个集合放入res,将4放入res[2]中。
当我们再回到第三层,遍历到5时,就不用再创建新集合了,直接将5放到到res[2]中即可。
之后,再将3放到res[1],6、7放入res[2]中。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
//用来存放最终结果
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
dfs(root,res,1);
Collections.reverse(res);
return res;
}
private void dfs(TreeNode root,ArrayList<List<Integer>> res,int index) {
if(root==null) {
return;
}
//如果index大于res大小,说明这一层没有对应的集合,需要新创建
if(index>res.size()) {
res.add(new ArrayList<Integer>());
}
//将当前层的元素直接放到对应层的末尾即可
res.get(index-1).add(root.val);
//继续遍历左右节点,同时将层高+1
dfs(root.left,res,index+1);
dfs(root.right,res,index+1);
}
}
16.二叉树的锯齿形层序遍历
题目链接:103. 二叉树的锯齿形层序遍历
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
树中节点数目在范围 [0, 2000] 内
-100 <= Node.val <= 100
题解:
方法:BFS
二叉树的BFS打印,就是一层一层的往下打印,因为这题要求的是先从左边,下一层从右边,在下一次从左边……左右交替的。我们可以使用一个变量leftToRight,如果是true就表示从左边开始打印,否则就从右边开始打印。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null)
return res;
//创建队列,保存节点
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);//先把节点加入到队列中
boolean leftToRight = true;//第一步先从左边开始打印
while (!queue.isEmpty()) {
//记录每层节点的值
List<Integer> level = new ArrayList<>();
//统计这一层有多少个节点
int count = queue.size();
//遍历这一层的所有节点,把他们全部从队列中移出来,顺便
//把他们的值加入到集合level中,接着再把他们的子节点(如果有)
//加入到队列中
for (int i = 0; i < count; i++) {
//poll移除队列头部元素(队列在头部移除,尾部添加)
TreeNode node = queue.poll();
//判断是从左往右打印还是从右往左打印。
if (leftToRight) {
//如果从左边打印,直接把访问的节点值加入到列表level的末尾即可
level.add(node.val);
} else {
//如果是从右边开始打印,每次要把访问的节点值
//加入到列表的最前面
level.add(0, node.val);
}
//左右子节点如果不为空会被加入到队列中
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
//把这一层的节点值加入到集合res中
res.add(level);
//改变下次访问的方向
leftToRight = !leftToRight;
}
return res;
}
}
方法:DFS
这题除了使用BFS以外,还可以使用DFS,但这里要有个判断,如果走到下一层的时候集合没有创建,要先创建下一层的集合。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
travel(root, res, 0);
return res;
}
private void travel(TreeNode cur, List<List<Integer>> res, int level) {
if (cur == null)
return;
//如果res.size() <= level说明下一层的集合还没创建,所以要先创建下一层的集合
if (res.size() <= level) {
List<Integer> newLevel = new LinkedList<>();
res.add(newLevel);
}
//遍历到第几层我们就操作第几层的数据
List<Integer> list = res.get(level);
//这里默认根节点是第0层,偶数层相当于从左往右遍历,
// 所以要添加到集合的末尾,如果是奇数层相当于从右往左遍历,
// 要把数据添加到集合的开头
if (level % 2 == 0)
list.add(cur.val);
else
list.add(0, cur.val);
//分别遍历左右两个子节点,到下一层了,所以层数要加1
travel(cur.left, res, level + 1);
travel(cur.right, res, level + 1);
}
}
17.N 叉树的层序遍历
题目链接:429. N 叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]
示例 2:
输入:root =
[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
提示:
树的高度不会超过 1000
树的节点总数在 [0, 10^4] 之间
题解:树的(层序)遍历运用题
方法:BFS
我们需要以「层」为单位构建答案,因此在单次 BFS 过程中也按层进行。
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> ans = new ArrayList<>();
Deque<Node> d = new ArrayDeque<>();
if (root != null) d.addLast(root);
while (!d.isEmpty()) {
int size = d.size();
List<Integer> list = new ArrayList<>();
while (size-- > 0) {
Node t = d.pollFirst();
for (Node node : t.children) d.addLast(node);
list.add(t.val);
}
ans.add(list);
}
return ans;
}
}
18.N 叉树的前序遍历
题目链接:589. N 叉树的前序遍历
给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
示例 2:
输入:root =
[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]输出:[1,2,3,6,7,11,14,4,8,12,5,9,13,10]
提示:
节点总数在范围 [0, 104]内
0 <= Node.val <= 104
n 叉树的高度小于或等于 1000
题解:
方法:递归
class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> preorder(Node root) {
dfs(root);
return ans;
}
void dfs(Node root) {
if (root == null) return ;
ans.add(root.val);
for (Node node : root.children) dfs(node);
}
}
方法:迭代
使用栈模拟递归过程。
迭代过程中记录 (node = 当前节点, cnt = 当前节点遍历过的子节点数量) 二元组,每次取出栈顶元素,如果当前节点是首次出队(当前遍历过的子节点数量为 cnt=0),则将当前节点的值加入答案,然后更新当前元素遍历过的子节点数量,并重新入队,即将 (node,cnt+1)入队,以及将下一子节点 (node.children[cnt],0) 进行首次入队。
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> ans = new ArrayList<>();
Deque<Object[]> d = new ArrayDeque<>();
d.addLast(new Object[]{root, 0});
while (!d.isEmpty()) {
Object[] poll = d.pollLast();
Node t = (Node)poll[0]; Integer cnt = (Integer)poll[1];
if (t == null) continue;
if (cnt == 0) ans.add(t.val);
if (cnt < t.children.size()) {
d.addLast(new Object[]{t, cnt + 1});
d.addLast(new Object[]{t.children.get(cnt), 0});
}
}
return ans;
}
}
方法:通用 递归 转 迭代
这是直接模拟系统执行递归的过程,这是一种更为通用的做法。在迭代过程中记录当前栈帧位置状态 loc,在每个状态流转节点做相应操作。
由于现代编译器已经做了很多关于递归的优化,现在这种技巧已经无须掌握。
class Solution {
public List<Integer> preorder(Node root) {
List<Integer> ans = new ArrayList<>();
Deque<Object[]> d = new ArrayDeque<>();
d.addLast(new Object[]{0, root});
while (!d.isEmpty()) {
Object[] poll = d.pollLast();
Integer loc = (Integer)poll[0]; Node t = (Node)poll[1];
if (t == null) continue;
if (loc == 0) {
ans.add(t.val);
d.addLast(new Object[]{1, t});
} else if (loc == 1) {
int n = t.children.size();
for (int i = n - 1; i >= 0; i--) {
d.addLast(new Object[]{0, t.children.get(i)});
}
}
}
return ans;
}
}