代码随想录刷题day13丨二叉树理论基础,递归遍历,迭代遍历,统一迭代,层序遍历
1.二叉树理论基础
1.1二叉树种类
-
满二叉树
-
概述:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
- 这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
-
度:连接节点和节点的线就是度,有n个节点,就有n-1个度,节点数总是比度要多一个,那么度为0的节点一定是叶子节点,因为该节点的下面不再有线;度为1的节点即:该节点只有一个分支;同理度为2的节点就是有两个分支。在二叉树中不可能存在度为3或大于3的节点!
-
-
完全二叉树
-
概述:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1至 2^(h-1) 个节点。
- 优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
-
-
二叉搜索树
-
概述:前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
-
下面这两棵树都是搜索树
-
-
平衡二叉搜索树
-
概述:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
-
图示
-
1.2存储方式
-
链式存储
-
存储方式:使用指针
-
链式存储是通过指针把分布在各个地址的节点串联一起
-
图示
-
-
顺序存储
-
存储方式:使用数组
-
顺序存储的元素在内存是连续分布的
-
图示
- 用数组来存储二叉树如何遍历的呢?
- 如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2
- 用数组来存储二叉树如何遍历的呢?
-
-
总结:用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树
1.3遍历方式
-
二叉树主要有两种遍历方式:
-
深度优先遍历:先往深走,遇到叶子节点再往回走。
-
广度优先遍历:一层一层的去遍历。
-
-
从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
-
深度优先遍历
-
前序遍历(递归法,迭代法)
-
中序遍历(递归法,迭代法)
-
后序遍历(递归法,迭代法)
-
图示
- 这里前中后,其实指的就是中间节点的遍历顺序
-
-
广度优先遍历
- 层序遍历(迭代法)
-
-
总结:
- 做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
- 栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的。
- 广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
1.4二叉树的定义
-
顺序存储就是用数组来存,没啥好说的
-
链式存储的二叉树节点的定义方式如下:
-
二叉树的定义 和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子。
-
在现场面试的时候 面试官可能要求手写代码,所以数据结构的定义以及简单逻辑的代码一定要锻炼白纸写出来。
-
因为我们在刷leetcode的时候,节点的定义默认都定义好了,真到面试的时候,需要自己写节点定义的时候,有时候会一脸懵逼!
-
代码
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; } }
TreeNode() {}
: 这是一个默认构造函数,它不带有任何参数。当你想创建一个没有任何初始值的节点时使用它。所有成员变量都会自动被初始化为它们类型的默认值(例如,对于整数类型的val
,它会被初始化为 0)。TreeNode(int val)
: 这个构造函数接受一个整数值作为参数,并将其赋值给节点的val
属性。left
和right
子节点被初始化为null
,表示该节点没有子节点。这是创建单个节点的简单方式。TreeNode(int val, TreeNode left, TreeNode right)
: 这个构造函数允许你同时设置节点的值和两个子节点。当你已经知道节点的值和它的子节点时使用这个构造函数。这通常在你已经构建好子树并且想要将它们连接到父节点时非常有用。
-
2.题目分类
3.递归遍历(144.前序遍历,145.后序遍历,94.中序遍历)
-
视频讲解:每次写递归都要靠直觉? 这次带你学透二叉树的递归遍历!| LeetCode:144.前序遍历,145.后序遍历,94.中序遍历_哔哩哔哩_bilibili
-
文档讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86.html
-
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
-
以下以前序遍历为例:
-
确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入List来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void preorder(TreeNode cur, List<Integer> result)
-
确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == null) { return; }
-
确定单层递归的逻辑:前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
result.add(cur.val); //中 preorder(cur.left, result); //左 preorder(cur.right, result); //右
-
前序遍历完整代码
void preorder(TreeNode cur, List<Integer> result){ if (cur == null) { return; } result.add(cur.val); //中 preorder(cur.left, result); //左 preorder(cur.right, result); //右 }
-
后序遍历完整代码
void postorder(TreeNode cur, List<Integer> result){ if (cur == null) { return; } postorder(cur.left, result); //左 postorder(cur.right, result); //右 result.add(cur.val); //中 }
-
中序遍历完整代码
void inorder(TreeNode cur, List<Integer> result){ if (cur == null) { return; } inorder(cur.left, result); //左 result.add(cur.val); //中 inorder(cur.right, result); //右 }
-
-
3.1前序遍历
-
代码(递归实现)
/** * 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<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); preorder(root,res); return res; } public void preorder(TreeNode cur,List<Integer> result){ if(cur == null){ return; } result.add(cur.val); //中 preorder(cur.left,result); //左 preorder(cur.right,result); //右 } }
3.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<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); postorder(root, res); return res; } void postorder(TreeNode cur, List<Integer> result) { if (cur == null) { return; } postorder(cur.left, result); // 左 postorder(cur.right, result); // 右 result.add(cur.val); // 中 } }
3.3中序遍历
-
代码(递归实现)
/** * 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<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); inorder(root, res); return res; } void inorder(TreeNode cur, List<Integer> result) { if (cur == null) { return; } inorder(cur.left, result); // 左 result.add(cur.val); // 中 inorder(cur.right, result); // 右 } }
4.循环遍历(144.前序遍历,145.后序遍历,94.中序遍历)
4.1前序遍历
-
视频讲解:
-
文档讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%BF%AD%E4%BB%A3%E9%81%8D%E5%8E%86.html
-
注意:
- 前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
-
代码(迭代实现)
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if(root == null){ return res; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.pop(); res.add(node.val); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } return res; } }
4.2后序遍历
-
注意:前序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下前序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中
-
代码(迭代实现)
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if(root == null){ return res; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.pop(); res.add(node.val); if (node.left != null) { stack.push(node.left); } if (node.right != null) { stack.push(node.right); } } Collections.reverse(res); return res; } }
4.3中序遍历
-
注意:迭代的过程中,其实我们有两个操作
- 处理:将元素放进res数组中
- 访问:遍历节点
- 前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!
-
代码(迭代实现)
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if(root == null){ return res; } Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root;//定义遍历指针 while(cur != null || !stack.isEmpty()){ if(cur != null){ stack.push(cur); cur = cur.left; }else{ cur = stack.pop(); res.add(cur.val); cur = cur.right; } } return res; } }
5.统一迭代
- 针对三种遍历方式,使用迭代法是可以写出统一风格的代码!
- 针对使用栈无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况,怎么办?
- 我们可以将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
- 如何标记?
- 就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
- 我们将访问的节点直接加入到栈中,但如果是处理的节点则后面放入一个空节点, 这样只有空节点弹出的时候,才将下一个节点放进结果集。
- 文档讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%BB%9F%E4%B8%80%E8%BF%AD%E4%BB%A3%E6%B3%95.html
5.1前序遍历
-
代码
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } }
5.2后序遍历
-
代码
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } }
5.3中序遍历
-
代码
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new LinkedList<>(); Stack<TreeNode> st = new Stack<>(); if (root != null) st.push(root); while (!st.empty()) { TreeNode node = st.peek(); if (node != null) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) st.push(node); // 添加中节点 st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.peek(); // 重新取出栈中元素 st.pop(); result.add(node.val); // 加入到结果集 } } return result; } }
6.层序遍历
- 概述:层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
- 需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
- 这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
- 视频讲解:讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode:102.二叉树的层序遍历_哔哩哔哩_bilibili
- 文档讲解:https://programmercarl.com/0102.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.html
6.1题目
学会二叉树的层序遍历,可以一口气打完以下十题:
- 102. 二叉树的层序遍历 - 力扣(LeetCode)
- 107. 二叉树的层序遍历 II - 力扣(LeetCode)
- 199. 二叉树的右视图 - 力扣(LeetCode)
- 637. 二叉树的层平均值 - 力扣(LeetCode)
- 429. N 叉树的层序遍历 - 力扣(LeetCode)
- 515. 在每个树行中找最大值 - 力扣(LeetCode)
- 116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)
- 117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)
- 104. 二叉树的最大深度 - 力扣(LeetCode)
- 111. 二叉树的最小深度 - 力扣(LeetCode)
6.1.1二叉树的层序遍历
-
题目
-
解题思路:用队列来实现
-
代码
class Solution { List<List<Integer>> resList = new ArrayList<>(); public List<List<Integer>> levelOrder(TreeNode root) { checkFun(root); return resList; } // 迭代方式 public void checkFun(TreeNode node) { if (node == null) { return; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(node); while (!que.isEmpty()) { // itemList 存每一行的结果 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); itemList.add(tmpNode.val); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } resList.add(itemList); } } }
6.1.2二叉树的层序遍历II
-
题目
-
解题思路:相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
-
代码
class Solution { List<List<Integer>> resList = new ArrayList<>(); public List<List<Integer>> levelOrderBottom(TreeNode root) { checkFun(root); List<List<Integer>> resList1 = new ArrayList<>(); for(int i = resList.size() -1;i >= 0;i--){ resList1.add(resList.get(i)); } return resList1; } // 迭代方式 public void checkFun(TreeNode node) { if (node == null) { return; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(node); while (!que.isEmpty()) { // itemList 存每一行的结果 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); itemList.add(tmpNode.val); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } resList.add(itemList); } } }
6.1.3二叉树的右视图
-
题目
-
解题思路:层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
-
代码
class Solution { List<List<Integer>> resList = new ArrayList<>(); public List<Integer> rightSideView(TreeNode root) { checkFun(root); List result = new ArrayList<>(); for(int i = 0;i < resList.size();i++){ List list = resList.get(i); result.add(list.get(list.size() -1)); } return result; } // 迭代方式 public void checkFun(TreeNode node) { if (node == null) { return; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(node); while (!que.isEmpty()) { // itemList 存每一行的结果 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); itemList.add(tmpNode.val); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } resList.add(itemList); } } }
6.1.4二叉树的层平均值
-
题目
-
解题思路:本题就是层序遍历的时候把一层求个总和再取一个均值。
-
代码
class Solution { List<List<Integer>> resList = new ArrayList<>(); public List<Double> averageOfLevels(TreeNode root) { checkFun(root); List<Double> result = new ArrayList<>(); for(int i = 0;i < resList.size();i++){ List<Integer> list = resList.get(i); double sum = 0; for(int j = 0;j <list.size();j++){ sum += list.get(j); } double average = sum / list.size(); result.add(average); } return result; } // 迭代方式 public void checkFun(TreeNode node) { if (node == null) { return; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(node); while (!que.isEmpty()) { // itemList 存每一行的结果 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); itemList.add(tmpNode.val); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } resList.add(itemList); } } }
6.1.5N叉树的层序遍历
-
题目
-
解题思路:这道题依旧是模板题,只不过一个节点有多个孩子了
-
代码
/* // 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>> resList = new ArrayList<>(); if(root == null){ return resList; } Queue<Node> que = new LinkedList<>(); que.offer(root); while(!que.isEmpty()){ //存储当前层的节点值 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while(len > 0){ Node tempNode = que.poll(); itemList.add(tempNode.val); //tempNode.children是一个包含所有子节点的列表 for(Node childreNode:tempNode.children){ que.add(childreNode); } len--; } resList.add(itemList); } return resList; } }
6.1.6在每个树行中找最大值
-
题目
-
解题思路:层序遍历,取每一层的最大值
-
代码
class Solution { List<List<Integer>> resList = new ArrayList<>(); public List<Integer> largestValues(TreeNode root) { checkFun(root); List result = new ArrayList<>(); for(int i = 0;i < resList.size();i++){ List<Integer> list = resList.get(i); // 使用 Collections.max() 求最大值 int maxValue = Collections.max(list); result.add(maxValue); } return result; } // 迭代方式 public void checkFun(TreeNode node) { if (node == null) { return; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(node); while (!que.isEmpty()) { // itemList 存每一行的结果 List<Integer> itemList = new LinkedList<>(); int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); itemList.add(tmpNode.val); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } resList.add(itemList); } } }
6.1.7填充每个节点的下一个右侧节点指针
-
题目
-
解题思路:本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
-
代码
/* // Definition for a Node. class Node { public int val; public Node left; public Node right; public Node next; public Node() {} public Node(int _val) { val = _val; } public Node(int _val, Node _left, Node _right, Node _next) { val = _val; left = _left; right = _right; next = _next; } }; */ class Solution { public Node connect(Node root) { Queue<Node> que = new LinkedList<Node>(); if (root == null) { return root; } que.add(root); while (!que.isEmpty()) { int len = que.size(); Node prev = null; // 用于跟踪前一个节点 while (len > 0) { Node cur = que.poll(); // 如果prev不为空,连接前一个节点的next指针到当前节点 if (prev != null) { prev.next = cur; } prev = cur; // 更新prev为当前节点 if (cur.left != null) { que.offer(cur.left); } if (cur.right != null) { que.offer(cur.right); } len--; } } return root; } }
6.1.8填充每个节点的下一个右侧节点指针II
-
题目
-
解题思路:这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道
-
代码
/* // Definition for a Node. class Node { public int val; public Node left; public Node right; public Node next; public Node() {} public Node(int _val) { val = _val; } public Node(int _val, Node _left, Node _right, Node _next) { val = _val; left = _left; right = _right; next = _next; } }; */ class Solution { public Node connect(Node root) { Queue<Node> que = new LinkedList<Node>(); if (root == null) { return root; } que.add(root); while (!que.isEmpty()) { int len = que.size(); Node prev = null; // 用于跟踪前一个节点 while (len > 0) { Node cur = que.poll(); // 如果prev不为空,连接前一个节点的next指针到当前节点 if (prev != null) { prev.next = cur; } prev = cur; // 更新prev为当前节点 if (cur.left != null) { que.offer(cur.left); } if (cur.right != null) { que.offer(cur.right); } len--; } } return root; } }
6.1.9二叉树的最大深度
-
题目
-
解题思路:使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
-
代码
class Solution { public int maxDepth(TreeNode root) { if (root == null) { return 0; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(root); int depth = 0; while (!que.isEmpty()) { int len = que.size(); while (len > 0) { TreeNode tmpNode = que.poll(); if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } depth++; } return depth; } }
6.1.10二叉树的最小深度
-
题目
-
解题思路:
-
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
-
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
-
-
代码
class Solution { public int minDepth(TreeNode root) { if (root == null) { return 0; } // 借助队列 Queue<TreeNode> que = new LinkedList<>(); que.offer(root); int depth = 0; while (!que.isEmpty()) { int len = que.size(); depth++; // 记录最小深度 while (len > 0) { TreeNode tmpNode = que.poll(); if(tmpNode.left == null && tmpNode.right == null){ return depth; } if (tmpNode.left != null) { que.offer(tmpNode.left); } if (tmpNode.right != null) { que.offer(tmpNode.right); } len--; } } return depth; } }