第十五天| 第六章 二叉树part02 层序遍历 10 101.对称二叉树
一、层序遍历
学会层序遍历,可以怒刷十题!!!
1.1 102. 二叉树的层序遍历
- 题目链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/
- 思路:
- 层序遍历提供两种思路解决:(DFS —> 递归、BFS —> 迭代)
- 在昨天的前序、中序和后序遍历中,使用的算法是图论中的深度优先算法(Deep First Search, DFS)
- 层序遍历使用的是广度优先算法(Breath First Search, BFS),可以用DFS的思路解决(递归)
- 层序遍历提供两种思路解决:(DFS —> 递归、BFS —> 迭代)
1.1.1 解法一:DFS —> 递归
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fV9acHQd-1692801864338)(层级遍历的DFS(递归)解法.png)]
按照递归三部曲写代码:
-
(1)确定递归函数的参数和返回值;
- 参数:需要将二叉树的根节点root参入函数,还需要设定一个层数deep用于衡量什么时候需要向二维列表中增添一维列表,还需要一个最终的结果返回列表resultList
-
(2)确定终止条件;
- 终止条件:如果root == null,return。
-
(3)确定单层递归的逻辑。
- 单层的递归逻辑,(注意这里的deep是否++,要看你传入的参数,把握不变量即可),接下来就是操作resultList的逻辑,分为两种情况:
- 第一种,如果resultList.size() < deep,需要构建一个一维列表item放入resultList中,因为要保证每一层都对应一个一维列表。
- 第二种,如果resultList.size() >= deep,说明resultList构建完成,只需要向里面填值。首先要做的是定位到第n层的一维列表,再add当前节点的val。resultList.get(dep - 1).add(root.val);
- 接下来,就是递归处理当前节点的左右子树。
- 单层的递归逻辑,(注意这里的deep是否++,要看你传入的参数,把握不变量即可),接下来就是操作resultList的逻辑,分为两种情况:
最终代码为:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
level(root, 0, resultList);
return resultList;
}
public void level(TreeNode root, Integer deep, List<List<Integer>> resultList) {
if (root == null) return;
deep++;
if (resultList.size() < deep) {
List<Integer> result = new ArrayList<>();
resultList.add(result);
}
resultList.get(deep - 1).add(root.val);
level(root.left, deep, resultList);
level(root.right, deep, resultList);
}
}
1.1.2 解法二:BFS —> 迭代
迭代的过程,是模拟一个队列。
-
思路:将每一层的节点入队,并记录每一层的size。对于每一层的逻辑是,让这一层的队首元素出列,记录队首元素的val,同时如果该节点的左右子节点不为空,就让他们入队。直到这一层的元素遍历完,即size==0,结束这一层的遍历。开启新的一轮,重复上面的操作。
-
终止的条件是什么呢?如果队列为空了,说明所有的元素都遍历了一次,此时结束。
-
代码:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> result = new ArrayList<>();
while (size-- > 0) {
TreeNode tempNode = queue.poll();
result.add(tempNode.val);
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
resultList.add(result);
}
return resultList;
}
}
1.2 107. 二叉树的层次遍历II
- 题目链接:https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/
- 题目介绍:给你二叉树的根节点
root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)- 示例:
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
- 思路:将上面的列表反过来输出即可
- 代码:
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> result = new ArrayList<>();
while (size-- > 0) {
TreeNode tempNode = queue.poll();
result.add(tempNode.val);
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
resultList.add(result);
}
List<List<Integer>> newResultList = new ArrayList<List<Integer>>();
for (int i = resultList.size() - 1; i >= 0; i--) {
newResultList.add(resultList.get(i));
}
return newResultList;
}
}
1.3 199. 二叉树的右视图
-
题目链接:https://leetcode.cn/problems/binary-tree-right-side-view/
-
题目介绍:给定一个二叉树的 根节点
root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXvnttiR-1692801864339)(右视图.png)]
-
输入: [1,2,3,null,5,null,4] 输出: [1,3,4]
-
-
代码:
-
第一种:这个代码和前面完全一样,就是把所有的节点层序遍历,然后再去每一层的最后一个结点的值。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> result = new ArrayList<>();
while (size-- > 0) {
TreeNode tempNode = queue.poll();
result.add(tempNode.val);
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
resultList.add(result);
}
List<Integer> newResult = new ArrayList<>();
for (int i = 0; i < resultList.size(); i++) {
int size = resultList.get(i).size();
newResult.add(resultList.get(i).get(size - 1));
}
return newResult;
}
}
- 第二种:层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
class Solution {
public List<Integer> rightSideView(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
List<Integer> result = new ArrayList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode tempNode = queue.poll();
if (size == 0) {
result.add(tempNode.val);
}
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
}
return result;
}
}
1.4 637. 二叉树的层平均值
- 题目链接:https://leetcode.cn/problems/average-of-levels-in-binary-tree/
- 题目介绍:给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。
- 代码:
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
List<Double> result = new ArrayList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int levelSize = queue.size();
double levelSum = 0.0;
for (int i = 0; i < levelSize; i++) {
TreeNode tempNode = queue.poll();
levelSum += tempNode.val;
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
result.add(levelSum / levelSize);
}
return result;
}
}
- 需要注意的关键点是:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TwxLQlm-1692801864340)(二叉树层平均值代码注意点.png)]
- 这里要用for循环代替while,千万不能使用while,因为我们要记录下来levelSize的值。
1.5 429. N叉树的层序遍历
-
题目链接:https://leetcode.cn/problems/n-ary-tree-level-order-traversal/
-
题目介绍:
- 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
- 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dPpSNbE-1692801864340)(N叉树.png)]
-
思路:
-
思路和之前一样,只要注意给定的N叉树的定义就能做出来
-
// 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) {
// 还是需要一个队列
Deque<Node> queue = new LinkedList<>();
// 还需要一个返回值
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> result = new ArrayList<>();
for (int i = 0; i < size; i++) {
Node tempNode = queue.poll();
result.add(tempNode.val);
if (tempNode.children != null) {
for (Node node : tempNode.children) {
queue.offer(node);
}
}
}
resultList.add(result);
}
return resultList;
}
}
1.6 515. 在每个树行中找最大值
- 题目链接:https://leetcode.cn/problems/find-largest-value-in-each-tree-row/
- 题目描述:给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
- 思路:
- 在每一层的循环外设定一个最大值,最大值是每一层队首元素的值,出队的时候和最大值比。如果比它大,就重新赋值。最后,把最大值add。
- 代码:
class Solution {
public List<Integer> largestValues(TreeNode root) {
// 还是需要一个队列
Deque<TreeNode> queue = new LinkedList<>();
// 还是需要一个结果集
List<Integer> result = new ArrayList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
int maxValue = queue.peek().val;
while (size-- > 0) {
TreeNode tempNode = queue.poll();
if (tempNode.val > maxValue) {
maxValue = tempNode.val;
}
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
result.add(maxValue);
}
return result;
}
}
1.7 116. 填充每个节点的下一个右侧节点指针
-
题目链接:https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/
-
题目描述:给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
-
struct Node { int val; Node *left; Node *right; Node *next; }
-
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为
NULL
。初始状态下,所有 next 指针都被设置为
NULL
。 -
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhXztV0X-1692801864341)(填充每个节点的下一个右侧节点指针116.png)]
-
-
思路:
-
首先观察LeetCode给出的Node定义
-
// 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; } };
-
-
思路就是:每次遍历到tempNode的时候填充它的next属性,判断一下是不是最后一个,最后一个next指针设为null,其余设为poll之后队列的首。
-
-
代码:
class Solution {
public Node connect(Node root) {
// 每次遍历到tempNode的时候填充它的右节点,判断一下是不是最后一个,最后一个next指针设为null
// 还是需要一个队列
Deque<Node> queue = new LinkedList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
Node tempNode = queue.poll();
if (size == 0) {
tempNode.next = null;
} else {
tempNode.next = queue.peek();
}
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
}
return root;
}
}
1.8 117. 填充每个节点的下一个右侧节点指针II
-
题目链接:https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/
-
题目描述:给定一个二叉树:
-
struct Node { int val; Node *left; Node *right; Node *next; }
-
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为
NULL
。初始状态下,所有 next 指针都被设置为
NULL
。
-
-
代码:(和上题一样)
-
class Solution { public Node connect(Node root) { // 每次遍历到tempNode的时候填充它的右节点,判断一下是不是最后一个,最后一个next指针设为null // 还是需要一个队列 Deque<Node> queue = new LinkedList<>(); if (root != null) { queue.offer(root); } while (!queue.isEmpty()) { int size = queue.size(); while (size-- > 0) { Node tempNode = queue.poll(); if (size == 0) { tempNode.next = null; } else { tempNode.next = queue.peek(); } if (tempNode.left != null) { queue.offer(tempNode.left); } if (tempNode.right != null) { queue.offer(tempNode.right); } } } return root; } }
-
1.9 104. 二叉树的最大深度
-
题目链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree/
-
题目介绍:
- 给定一个二叉树
root
,返回其最大深度。 - 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
- 给定一个二叉树
-
思路:
- 第一种:采用递归DFS的思路,resultList的大小就是二叉树最大的深度。
- 第二种:采用迭代BFS的思路,内循环的次数就是二叉树的最大深度。
-
代码:
-
第一种:DFS
-
class Solution { public int maxDepth(TreeNode root) { // 采用DFS试试 List<List<Integer>> resultList = new ArrayList<List<Integer>>(); travel(root, 0, resultList); return resultList.size(); } public void travel (TreeNode root, int deep, List<List<Integer>> resultList) { if (root == null) return; deep++; if (resultList.size() < deep) { List<Integer> item = new ArrayList<>(); resultList.add(item); } travel(root.left, deep, resultList); travel(root.right, deep, resultList); } }
-
-
第二种:BFS
-
class Solution { public int maxDepth(TreeNode root) { Deque<TreeNode> queue = new LinkedList<>(); int depth = 0; if (root != null) { queue.offer(root); } while (!queue.isEmpty()) { int size = queue.size(); while (size-- > 0) { TreeNode tempNode = queue.poll(); if (tempNode.left != null) { queue.offer(tempNode.left); } if (tempNode.right != null) { queue.offer(tempNode.right); } } depth++; } return depth; } }
-
-
1.10 111. 二叉树的最小深度
- 题目链接:https://leetcode.cn/problems/minimum-depth-of-binary-tree/
- 题目介绍:
- 给定一个二叉树,找出其最小深度。
- 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
- **说明:**叶子节点是指没有子节点的节点。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1b5q5XN0-1692801864341)(二叉树最小深度.png)]
- 思路:
- 找到最近的叶子节点的位置,然后返回当前的所在的depth。
- 代码:
class Solution {
public int minDepth(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
int depth = 0;
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode tempNode = queue.poll();
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
if (tempNode.left == null && tempNode.right == null) {
depth++;
return depth;
}
}
depth++;
}
return depth;
}
}
二、226.翻转二叉树
-
题目链接:https://leetcode.cn/problems/invert-binary-tree/
-
题目描述:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UwNT1jq9-1692801864341)(翻转二叉树.png)]
-
思路:
- 可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
- 关键在于遍历顺序,前中后序应该选哪一种遍历顺序?还是选择层序遍历呢?
- 答案是:前序、后序和层序都可以,唯独中序不可以
- 为什么中序不可以呢?
- 因为中序在交换的时候,左子树已经处理完了。如果在左子树处理完再交换,然后再去处理右子树,这个时候的右子树已经交换过了,即原来的左子树。最终的效果如下图:
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IOJKqMeN-1692801864342)(中序遍历翻转最终失败的结果.png)]
- 前序后序可以分别使用DFS(递归)、BFS(迭代)
- 也可以采用层序。
-
代码:
- (1)DFS
// 前序遍历递归翻转 class Solution { public TreeNode invertTree(TreeNode root) { if (root == null) return null; swapChildren(root); invertTree(root.left); invertTree(root.right); return root; } private void swapChildren(TreeNode root) { TreeNode tmp = root.left; root.left = root.right; root.right = tmp; } }
// 后序遍历递归翻转 class Solution { public TreeNode invertTree(TreeNode root) { if (root == null) return null; invertTree(root.left); invertTree(root.right); swapChildren(root); return root; } private void swapChildren(TreeNode root) { TreeNode tmp = root.left; root.left = root.right; root.right = tmp; } }
- (2)BFS
class Solution { public TreeNode invertTree(TreeNode root) { Deque<TreeNode> stack = new LinkedList<>(); TreeNode pop = null; TreeNode cur = root; while (cur != null || !stack.isEmpty()) { if (cur != null) { stack.push(cur); // 前序遍历迭代翻转 // swapChildren(cur); cur = cur.left; } else { TreeNode peek = stack.peek(); if (peek.right == null) { pop = stack.pop(); // 后序遍历迭代翻转 swapChildren(pop); } else if (peek.right == pop) { pop = stack.pop(); // 后序遍历迭代翻转 swapChildren(pop); } else { cur = peek.right; } } } return root; } private void swapChildren(TreeNode root) { TreeNode tmp = root.left; root.left = root.right; root.right = tmp; } }
- (3)层序遍历
class Solution { public TreeNode invertTree(TreeNode root) { Deque<TreeNode> queue = new LinkedList<>(); if (root != null) { queue.offer(root); } while (!queue.isEmpty()) { int size = queue.size(); while (size-- > 0) { TreeNode tempNode = queue.poll(); swapChildren(tempNode); if (tempNode.left != null) { queue.offer(tempNode.left); } if (tempNode.right != null) { queue.offer(tempNode.right); } } } return root; } private void swapChildren(TreeNode root) { TreeNode tmp = root.left; root.left = root.right; root.right = tmp; } }
三、101.对称二叉树
-
题目链接:https://leetcode.cn/problems/symmetric-tree/
-
题目描述:给你一个二叉树的根节点
root
, 检查它是否轴对称。- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-acQTOPY9-1692801864342)(对称二叉树.png)]
-
思路:
- 首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
- 而是左右子树
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRTWJ2f1-1692801864342)(对称二叉树比较的是什么.png)]
- 即什么情况下判断这个二叉树是一个对称二叉树?左子树的左节点和右子树的右节点相等,左子树的右节点和右子树的左节点相等。
- 这里的遍历顺序是什么呢?很明显,我们需要左节点和右节点的值,再去确定是否接着递归,所以是左中右的顺序,即后序遍历。
- 接下来我们要明确什么情况下递归会return false,什么情况下return true。
- return false的情况有三种:左空,右不空;左不空,右空;左右不空,但值不相等;
- return true的情况有两种:左空,右也空;左的左节点等于右的右节点,并且左的右节点等于右的左节点
- 按照上面的思路编写代码
-
代码:
class Solution { public boolean isSymmetric(TreeNode root) { return compare(root.left, root.right); } private boolean compare(TreeNode left, TreeNode right) { if (left == null && right != null) { return false; } if (left != null && right == null) { return false; } if (left == null && right == null) { return true; } if (left.val != right.val) { return false; } // 比较外侧 boolean compareOutside = compare(left.left, right.right); // 比较内侧 boolean compareInside = compare(left.right, right.left); return compareOutside && compareInside; } }