目录
二叉树部分的题目分类大纲如图所示:
一、理论基础
1.二叉树的种类
1.1 满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
1.2 完全二叉树
完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
即完全二叉树的最下面一层必须是从左往右连续的,可以不填满。
优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
满二叉树是完全二叉树。
1.3 二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树
1.4 平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意unordered_map、unordered_set底层实现是哈希表。
2. 二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
2.1 链式存储(指针)(常用)
一般都是用链式存储二叉树。
2.2 顺序存储(数组)
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
3. 二叉树的遍历方式
做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的。
广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
3.1 深度优先遍历(递归,迭代)
深度优先遍历:先往深走,遇到叶子节点再往回走。(可用栈实现)
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
前中后序指的就是中间节点的位置。
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
3.2 广度优先遍历(迭代)
广度优先遍历:一层一层的去遍历。(可用队列实现)
- 层次遍历(迭代法)
4. 二叉树的定义
链式存储的二叉树节点的定义方式:
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;
}
}
在现场面试的时候 面试官可能要求手写代码,所以数据结构的定义以及简单逻辑的代码一定要锻炼白纸写出来。
因为我们在刷leetcode的时候,节点的定义默认都定义好了,真到面试的时候,需要自己写节点定义的时候,有时候会一脸懵逼!
二、递归遍历
1. 递归三要素
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。一般返回 void ,参数在迭代的过程中更新了。
-
确定终止条件: 写完了递归算法,运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
2.递归实现深度优先遍历
修改逻辑顺序便可实现前后中序。
以 前序遍历 为例,C++:(确定三要素)
1. 确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void traversal(TreeNode* cur, vector<int>& vec)
2. 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;
3. 确定单层递归的逻辑:前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
注:递归顺序不要乱!
144. 二叉树的前序遍历(递归法)
核心是preorder()函数
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.val); //中
preorder(root.left, result); //前
preorder(root.right, result); //后
}
}
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;
}
}
94. 二叉树的中序遍历(递归法)
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
inorder(root, result);
return result;
}
public void inorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
inorder(root.left, result); //前
result.add(root.val); //中
inorder(root.right, result); //后
}
}
145. 二叉树的后序遍历(递归法)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
postorder(root, result);
return result;
}
public void postorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
postorder(root.left, result); //前
postorder(root.right, result); //后
result.add(root.val); //中
}
}
三、迭代遍历
用栈实现。用迭代法时,前序和后序遍历的逻辑可以修改顺序直接应用,但是无法直接应用于中序上。因为在迭代的过程中,其实有两个操作:
- 处理:将元素放进result数组中
- 访问:遍历节点
分析一下为什么刚刚写的前序(后序)遍历的代码,不能和中序遍历通用呢?
前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!
因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
144. 二叉树的前序遍历(迭代法)
思路:
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val); //中
if (node.right != null){
stack.push(node.right); //右(空节点不入栈)
}
if (node.left != null){
stack.push(node.left); //左(空节点不入栈)
}
}
return result;
}
}
145. 二叉树的后序遍历(迭代法)
Java 使用Collections.reverse()对list集合进行反转
思路:
再来看后序遍历,先序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val); //中
if (node.left != null){
stack.push(node.left); //左(空节点不入栈)
}
if (node.right != null){
stack.push(node.right); //右(空节点不入栈)
}
}
Collections.reverse(result);
return result;
}
}
94. 二叉树的中序遍历(迭代法)
思路:
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
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(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
result.add(cur.val); // 中
cur = cur.right; // 右
}
}
return result;
}
}
四、统一迭代遍历
二、中的递归遍历可以统一风格,实现前中后序遍历只需修改逻辑顺序。
三、中的迭代遍历却不能,前后序可通过修改顺序实现,中序不符合。
因此四、提出一种统一风格的迭代遍历方法,实现前中后序的遍历。就是有点抽象,不好理解。
思路:
就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢?就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
仅仅改变顺序即可实现前中后序的统一迭代遍历。
- 迭代法前序遍历代码如下:
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;
}
}
- 迭代法中序遍历代码如下:
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); } // 添加右节点(空节点不入栈)
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;
}
}
迭代法后序遍历代码如下:
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(); // 将该节点弹出,避免重复操作,下面再将中右左节点添加到栈中
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;
}
}
五、层序遍历
102.二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
(借助队列的迭代方式好理解,但是递归方式有点看不懂)
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
checkFun01(root, 0);
// checkFun02(root);
return resList;
}
//BFS--递归方式
public void checkFun01(TreeNode node, Integer deep) {
if (node == null) {
return;
}
deep++;
if (resList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List<Integer> item = new ArrayList<>();
resList.add(item);
}
resList.get(deep - 1).add(node.val);
checkFun01(node.left, deep);
checkFun01(node.right, deep);
}
//BFS--迭代方式--借助队列
public void checkFun02(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
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);
}
}
}
107.二叉树的层次遍历 II
注意二维列表:
public static void main(String[] args) {
List<List<Integer>> resList = new ArrayList<>();
// 初始化 resList 的值
resList.add(new ArrayList<>(Arrays.asList(15, 7)));
resList.add(new ArrayList<>(Arrays.asList(9, 20)));
resList.add(new ArrayList<>(Arrays.asList(3)));
// 输出结果查看
System.out.println(resList);
System.out.println(resList.size());
System.out.println(resList.get(0));
System.out.println(resList.get(0).size());
}
输出结果:
[[15, 7], [9, 20], [3]]
3
[15, 7]
2
思路1:相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
class Solution {
public List<List<Integer>> resList = new ArrayList<>();
public List<List<Integer>> levelOrderBottom(TreeNode root) {
// 层序遍历,再翻转数组即可。
checkFun(root);
List<List<Integer>> revResList = new ArrayList<>();
for(int i = resList.size()-1;i>=0;i--){
revResList.add(resList.get(i));
}
return revResList;
}
public void checkFun(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
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);
}
}
resList.add(itemList);
}
}
}
思路2:思路和模板相同, 对收集答案的方式做了优化, 最后不需要反转。借助链表实现。
class Solution {
public List<List<Integer>> resList = new ArrayList<>(); // 思路1
// 利用链表可以进行 O(1) 头部插入, 这样最后答案不需要再反转
public LinkedList<List<Integer>> ans = new LinkedList<>(); // 思路2
public List<List<Integer>> levelOrderBottom(TreeNode root) {
// 思路1:层序遍历,再翻转数组即可。
// checkFun1(root);
// List<List<Integer>> revResList = new ArrayList<>();
// for(int i = resList.size()-1;i>=0;i--){
// revResList.add(resList.get(i));
// }
// return revResList;
// 思路2:借助链表,直接进行头部插入,不需反转
checkFun2(root);
return ans;
}
// public void checkFun1(TreeNode node) {
// if (node == null) {
// return;
// }
// Queue<TreeNode> que = new LinkedList<TreeNode>();
// que.offer(node);
//
// while (!que.isEmpty()) {
// List<Integer> itemList = new ArrayList<>();
// 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);
// }
// }
// resList.add(itemList);
// }
// }
public void checkFun2(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
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);
}
}
// 新遍历到的层插到头部, 这样就满足按照层次反序的要求
ans.addFirst(itemList);
}
}
}
199.二叉树的右视图
思路:层序遍历的时候,判断是否遍历到单层的最后面的元素 if(i == len - 1),如果是,就放进result数组中,随后返回result就可以了。
class Solution {
public List<Integer> resList = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
checkFun02(root);
return resList;
}
public void checkFun02(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
int len = que.size();
for (int i = 0; i < len; i++) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) {
que.offer(tmpNode.left);
}
if (tmpNode.right != null) {
que.offer(tmpNode.right);
}
if (i == len - 1) {
resList.add(tmpNode.val);
}
}
}
}
}
637.二叉树的层平均值
本题就是层序遍历的时候把一层求个总和再取一个均值。
class Solution {
public List<Double> averageList = new ArrayList<>();
public List<Double> averageOfLevels(TreeNode root) {
checkFun02(root);
return averageList;
}
public void checkFun02(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
int len = que.size();
int size = que.size();
double sum = 0;
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
sum += tmpNode.val;
if (tmpNode.left != null) {
que.offer(tmpNode.left);
}
if (tmpNode.right != null) {
que.offer(tmpNode.right);
}
len--;
}
averageList.add(sum/size);
}
}
}
429.N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
可以多看看,自己写不出来,但是比较容易看懂,多理解一下。
public class N0429 {
/**
* 解法:队列,迭代。
*/
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> list = new ArrayList<>();
Deque<Node> que = new LinkedList<>();
if (root == null) {
return list;
}
que.offerLast(root);
while (!que.isEmpty()) {
int levelSize = que.size();
List<Integer> levelList = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
Node poll = que.pollFirst();
levelList.add(poll.val);
List<Node> children = poll.children;
if (children == null || children.size() == 0) {
continue;
}
for (Node child : children) {
if (child != null) {
que.offerLast(child);
}
}
}
list.add(levelList);
}
return list;
}
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;
}
}
}
515.在每个树行中找最大值
层序遍历,取每一层的最大值
class Solution {
public List<Integer> resList = new ArrayList<>();
public List<Integer> largestValues(TreeNode root) {
checkFun02(root);
return resList;
}
public void checkFun02(TreeNode node) {
if (node == null) {
return;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(node);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<>();
int len = que.size();
int max = que.peek().val;
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
max = max > tmpNode.val ? max : tmpNode.val;
if (tmpNode.left != null) {
que.offer(tmpNode.left);
}
if (tmpNode.right != null) {
que.offer(tmpNode.right);
}
len--;
}
resList.add(max);
}
}
}
简洁版:
借助 int max = Integer.MIN_VALUE; 进行初始化赋值。
借助 Math.max() 进行比较得到最大值。
class Solution {
public List<Integer> largestValues(TreeNode root) {
if(root == null){
return Collections.emptyList();
}
List<Integer> result = new ArrayList();
Queue<TreeNode> queue = new LinkedList();
queue.offer(root);
while(!queue.isEmpty()){
int max = Integer.MIN_VALUE;
for(int i = queue.size(); i > 0; i--){
TreeNode node = queue.poll();
max = Math.max(max, node.val);
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
result.add(max);
}
return result;
}
}
116.填充每个节点的下一个右侧节点指针
思路:本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了。
class Solution {
public Node connect(Node root) {
Queue<Node> tmpQueue = new LinkedList<Node>();
if (root != null) {
tmpQueue.add(root);
}
while (tmpQueue.size() != 0) {
int size = tmpQueue.size();
Node cur = tmpQueue.poll(); //处理每一层的第一个节点
if (cur.left != null) {
tmpQueue.add(cur.left);
}
if (cur.right != null) {
tmpQueue.add(cur.right);
}
for (int index = 1; index < size; index++) { //遍历当前层的其余节点.每次循环从队列中取出下一个节点next,并将其左子节点和右子节点(如果有)加入队列。
Node next = tmpQueue.poll();
if (next.left != null) {
tmpQueue.add(next.left);
}
if (next.right != null) {
tmpQueue.add(next.right);
}
cur.next = next;
cur = next;
}
}
return root; //函数返回经过修改的根节点root
}
}
117.填充每个节点的下一个右侧节点指针II
与116一样
class Solution {
public Node connect(Node root) {
Queue<Node> queue = new LinkedList<>();
if (root != null) {
queue.add(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
Node node = null;
Node nodePre = null;
for (int i = 0; i < size; i++) {
if (i == 0) {
nodePre = queue.poll(); // 取出本层头一个节点
node = nodePre;
} else {
node = queue.poll();
nodePre.next = node; // 本层前一个节点 next 指向当前节点
nodePre = nodePre.next;
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
nodePre.next = null; // 本层最后一个节点 next 指向 null
}
return root;
}
}
104.二叉树的最大深度
思路:使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
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 node = que.poll();
if (node.left != null) {
que.offer(node.left);
}
if (node.right != null) {
que.offer(node.right);
}
}
depth++;
}
return depth;
}
}
111.二叉树的最小深度
相对于 104.二叉树的最大深度 ,本题也可以使用层序遍历的方式来解决,思路是一样的。
注意:只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点。
class Solution {
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()) {
int size = queue.size();
depth++; // 记录最小深度
TreeNode cur = null;
for (int i = 0; i < size; i++) {
cur = queue.poll();
//如果当前节点的左右孩子都为空,直接返回最小深度
if (cur.left == null && cur.right == null) {
return depth;
}
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
return depth;
}
}
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时又发现队列的一个应用了)。
第十三天的总算是结束了,直冲Day14!