算法通关村系列文章目录
文章目录
前言
本系列文章是针对于鱼皮知识星球——编程导航中的算法通关村中的算法进行归纳和总结。 该篇文章讲解的是第六关中的白银挑战———二叉树层次遍历经典问题
树的层次遍历问题核心就是利用队列获取到树的每一层的元素,并且可以对每一层的元素进行一些其他的操作。我们还是像往常一样,通过LeetCode的题目进行练习
一、基础的层次遍历
层次遍历就是对二叉树一层一层地输出,对于这个问题,我们可以用队列很好的解决
3
/ \
9 20
/ \
15 7
当一个节点被弹出队列后,如果他的左右节点不为空,再把它的左右节点放进队列中,因为队列是先进先出,后添加的节点会在前一层弹出后,才会轮到
这也符合层次遍历的特点。就相当于从根节点开始按层将节点放到队列中。然后依次弹出。整个过程应该还是比较好理解的
LeetCode102:
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
这道题就是最基本的层序遍历,只是每一层的元素我们需要用一个List给他装起来就行
步骤
- 先获取队列的元素的个数,然后针对这些元素开一个循环处,这样 for 循环里面就是每一层的元素
代码:
public static List<List<Integer>> level102Order(TreeNode root){
if(root==null) return null;
// 存放节点的队
LinkedList<TreeNode> queue = new LinkedList<>();
// 先放到temp中,等queue全导出了再把temp放到queue
queue.add(root);
List<List<Integer>> result=new ArrayList<>();
if(root.left==null&&root.right==null){
List<Integer> roots = new ArrayList<>();
roots.add(root.val);
result.add(roots);
return result;
}
while (queue.size()>0){
int size= queue.size();
List<Integer> integers = new ArrayList<>();
for (int i = 1; i <=size ; i++) {
TreeNode poll = queue.poll();
integers.add(poll.val);
TreeNode left = poll.left;
TreeNode right = poll.right;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
result.add(integers);
}
return result;
}
二、层序遍历—自定向上
Leetcode 107:
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
这道题跟刚才的那道题几乎一模一样,只是输出的时候倒过来输出而已。
那我们看,我们刚才添加元素的时候,是直接把元素扔到了一个ArrayList里面,那ArrayList是默认往后插入的,那我们这里只需要改成往头插就好了,往头插!!!这不就是我们在第二关链表反转中写的头插法嘛,那java中有对应好的LinkedList,直接拿来用就好了,不用我们再手写了。
转换为代码
public static List<List<Integer>> levelOrderBottom(TreeNode root){
// 存放节点的队
LinkedList<TreeNode> queue = new LinkedList<>();
// 先放到temp中,等queue全导出了再把temp放到queue
queue.add(root);
LinkedList<List<Integer>> result=new LinkedList<>();
if(root==null) return null;
if(root.left==null&&root.right==null){
List<Integer> roots = new ArrayList<>();
roots.add(root.val);
result.add(roots);
return result;
}
while (queue.size()>0){
int size= queue.size();
List<Integer> integers = new ArrayList<>();
for (int i = 1; i <=size ; i++) {
TreeNode poll = queue.poll();
integers.add(poll.val);
TreeNode left = poll.left;
TreeNode right = poll.right;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
result.addFirst(integers);
}
return result;
}
三、二叉树的锯齿形层次遍历
LeetCode 103:
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]
这个锯齿形遍历,其实就是一层是从左向右遍历,一层是从右向左遍历,交替执行就好了。其实我们看,有关大部分的层次遍历题目我们只需要能够获取每层的元素,其他的问题基本上就都能解决。
步骤:
- 我们只用一个 bool 变量 r 控制,当 r 为 true时,元素往队列的尾部插入,为 false时,元素往队列的头部插入。
- r 每一层变换一次,也就是每一层要么往前插入,要么往后插入
- 往后插入就是正常的从左向右,如果要换成从右向左,其实就是还是从左向右,只是往前插,那么最先插入的就到尾部了,就实现了从右向左的效果
代码:
public static List<List<Integer>> zigzagLevelOrder(TreeNode root) {
// 存放节点的队
LinkedList<TreeNode> queue = new LinkedList<>();
// 先放到temp中,等queue全导出了再把temp放到queue
queue.add(root);
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
if (root.left == null && root.right == null) {
List<Integer> roots = new ArrayList<>();
roots.add(root.val);
result.add(roots);
return result;
}
boolean r = true;
while (queue.size() > 0) {
int size = queue.size();
Deque<Integer> integers = new LinkedList<>();
for (int i = 1; i <= size; i++) {
TreeNode poll = queue.poll();
TreeNode right = poll.right;
TreeNode left = poll.left;
if(r){
integers.offerLast(poll.val);
}else {
integers.offerFirst(poll.val);
}
if (left != null) {
queue.add(left);
}
if (right != null) {
queue.add(right);
}
}
result.add(new ArrayList<>(integers));
r = !r;
}
return result;
}
四、在每一层找最大值
Leetcode. 515:
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
这个找最大值,其实当我们能把每一层的元素找到后,这个问题就仅仅只是一个找最大值的问题,核心问题还是找到每一层的元素,只要可以用双端队列或者栈找到每一层的元素,最大值应该就不是问题
代码:
public static List<Integer> largestValues(TreeNode root) {
List<Integer> results = new ArrayList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (queue.size()>0){
int size=queue.size();
int max=Integer.MIN_VALUE;
// 找每一层的元素
for (int i = 1; i <=size ; i++) {
TreeNode poll = queue.poll();
if(poll.val>=max) max= poll.val;
TreeNode right = poll.right;
TreeNode left = poll.left;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
results.add(max);
}
return results;
}
五、在每一层找平均值
LeetCode 637:二叉树的层平均值
输入:root = [3,9,20,null,null,15,7]
输出:[3.00000,14.50000,11.00000]
解释:第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。
因此返回 [3, 14.5, 11] 。
这个平均值跟最大值差不多,只是把求最大值的操作换成了求平均值。思想还是一样的,只要掌握了求每一层元素的方法,后续加多少操作都是可以完成的。
public static List<Double> averageOfLevels(TreeNode root) {
List<Double> results=new ArrayList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (queue.size()>0){
int size=queue.size();
double average=0;int time=0;
for (int i = 1; i <=size; i++) {
TreeNode poll = queue.poll();
average+= poll.val;
time++;
TreeNode left = poll.left;
TreeNode right = poll.right;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
average=average/time;
results.add(average);
}
return results;
}
六、二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
这道题就比较有难度了,这里先解释一下题意,右视图就相当于从右侧看这棵树,所以,如果一层只有一个节点,右视图也是可以看到的。但是我们遍历的时候是从左往右遍历的,也就是最右边的是在最后的,那么我们在遍历一层的时候,只需要一直遍历就好了,最后的那个节点一定是这一层的最右侧的那个节点
public static List<Integer> rightSideView(TreeNode root) {
LinkedList<TreeNode>queue = new LinkedList<>();
List<Integer> result=new ArrayList<>();
queue.add(root);
while (queue.size()>0){
int size = queue.size();
int rightest=0;
for (int i = 1; i <=size; i++) {
TreeNode poll = queue.poll();
rightest=poll.val;
TreeNode right = poll.right;
TreeNode left = poll.left;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
result.add(rightest);
}
return result;
}
七、找树左下角的值
LeetCode 513:
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
这道题跟上面那道右视图其实是一个套路,只是上一道题是每一层的右节点,这个换成了最底层的左节点,其实方法还是一样的,只是这里我们换成最左侧的节点,其实也不用把刚才右视图的插入顺序反过来,其实我们这里只用每一层纸添加第一个节点,那么最后一个遍历到的一定是最后一层的左节点
public static int findBottomLeftValue(TreeNode root) {
LinkedList<TreeNode> queue = new LinkedList<>();
List<Integer> result=new ArrayList<>();
queue.add(root);
int leftBottom=0;
while (queue.size()>0){
int size = queue.size();
int leftest=0;
for (int i = 1; i <=size; i++) {
TreeNode poll = queue.poll();
if(i==1) leftest=poll.val;
TreeNode right = poll.right;
TreeNode left = poll.left;
if(left!=null){
queue.add(left);
}
if(right!=null){
queue.add(right);
}
}
leftBottom=leftest;
}
return leftBottom;
}
总结
通过上面几道题的练习,相信大家已经理解了对于二叉树的层序遍历问题其实最主要的就是能够处理好每一层的元素,只要能够处理好每一层的元素,其实后面的题在怎么变形也都只是在遍历的基础上增加。那这里关于二叉树的层序遍历问题我们在这里就先搞一段落了,后面将继续更新有关二叉树的深度优先经典问题。
我是Mayphyr,从一点点到亿点点,我们下次再见