文章目录
代码随想录算法训练营day15
层序遍历专题
思路
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ARPmALk-1687871390517)(https://note.youdao.com/yws/res/89011/WEBRESOURCE3a27c0fde7df16d52b8da4ba9fc6ca74)]
二叉树的层序遍历
https://leetcode.cn/problems/binary-tree-level-order-traversal/
题目
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路
代码
迭代
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(root);
int size;
while(!q.isEmpty()){
List<Integer> tmp = new ArrayList<>();
size = q.size();
while(size-- > 0){
TreeNode node = q.poll();
tmp.add(node.val);
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
res.add(tmp);
}
return res;
}
}
递归
class Solution {
public List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
dfs(root,0);
return res;
}
public void dfs(TreeNode root , int deep){
if (root == null) return;
deep++;
if(res.size() < deep){
res.add(new ArrayList<>());
}
res.get(deep-1).add(root.val);
dfs(root.left, deep);
dfs(root.right, deep);
}
}
二叉树的层次遍历 II
题目
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
示例 1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RWmgfpF4-1687871390518)(https://note.youdao.com/yws/res/89019/WEBRESOURCEaafc574cae000bdfe23379a147b7288e)]
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
思路
相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
代码
result数组反转
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if (root == null) return list;
Queue<TreeNode> nodeList = new LinkedList<>();
nodeList.offer(root);
int size;
while(!nodeList.isEmpty()){
List<Integer> tmp = new ArrayList<>();
size = nodeList.size();
while(size -- > 0){
TreeNode node = nodeList.poll();
tmp.add(node.val);
if(node.left != null) nodeList.offer(node.left);
if(node.right != null) nodeList.offer(node.right);
}
list.add(tmp);
}
List<List<Integer>> res = new ArrayList<>();
for (int i = list.size() - 1; i >= 0; i-- ) {
res.add(list.get(i));
}
return res;
}
}
不需要反转(用链表)
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
// 利用链表可以进行 O(1) 头部插入, 这样最后答案不需要再反转
LinkedList<List<Integer>> res = new LinkedList<>();
if (root == null) return res;
Queue<TreeNode> nodeList = new LinkedList<>();
nodeList.offer(root);
int size;
while(!nodeList.isEmpty()){
List<Integer> tmp = new ArrayList<>();
size = nodeList.size();
while(size -- > 0){
TreeNode node = nodeList.poll();
tmp.add(node.val);
if(node.left != null) nodeList.offer(node.left);
if(node.right != null) nodeList.offer(node.right);
}
res.addFirst(tmp);
}
return res;
}
}
二叉树的右视图
https://leetcode.cn/problems/binary-tree-right-side-view/
题目
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
代码
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
for(int i = 0; i < size; i++){
TreeNode node = q.poll();
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
if(i == size - 1){
res.add(node.val);
}
}
}
return res;
}
}
二叉树的层平均值
https://leetcode.cn/problems/average-of-levels-in-binary-tree/
题目
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
示例 1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KBGdRf58-1687871390518)(https://note.youdao.com/yws/res/89042/WEBRESOURCE4a64121a46860fb4d8e79d481d3fadcc)]
输入: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] 。
思路
层序遍历的时候把一层求个总和在取一个均值
代码
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
Double sum = 0.0;
for(int i = 0; i < size; i++){
TreeNode node = q.poll();
sum += node.val;
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
res.add(sum / size);
}
return res;
}
}
N叉树的层序遍历
https://leetcode.cn/problems/n-ary-tree-level-order-traversal/
题目
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpqjPIcu-1687871390518)(https://note.youdao.com/yws/res/89053/WEBRESOURCE06c89eb997f3069d336461af48dc84d0)]
返回其层序遍历:
[ [1], [3,2,4], [5,6] ]
思路
这道题依旧是模板题,只不过一个节点有多个孩子了
代码
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<Node> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
List<Integer> tmp = new ArrayList<>();
while(size-- > 0){
Node node = q.poll();
tmp.add(node.val);
List<Node> children = node.children;
if(children == null || children.size() == 0){
continue;
}
for (Node child : children) {
if (child != null) {
q.offer(child);
}
}
}
res.add(tmp);
}
return res;
}
}
在每个树行中找最大值
题目
您需要在二叉树的每一行中找到最大的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEdKvU8T-1687871390519)(https://note.youdao.com/yws/res/89061/WEBRESOURCEd639831c5b1da1f90583a4ce05e4f449)]
思路
层序遍历,取每一层的最大值
代码
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
int max = Integer.MIN_VALUE;
while(size-- > 0){
TreeNode node = q.poll();
max = Math.max(max, node.val);
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
res.add(max);
}
return res;
}
}
二叉树的最大深度
https://leetcode.cn/problems/maximum-depth-of-binary-tree/
题目
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K9CLJJVG-1687871390519)(https://note.youdao.com/yws/res/89071/WEBRESOURCE5f5a01bc2408bfaace0a1c44b9a43ae0)]
返回它的最大深度 3 。
思路
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
代码
class Solution {
public int maxDepth(TreeNode root) {
int deep = 0;
if (root == null) return deep;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
deep++;
while(size-- > 0){
TreeNode node = q.poll();
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
}
return deep;
}
}
二叉树的最小深度
题目
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
思路
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
代码
class Solution {
public int minDepth(TreeNode root) {
int deep = 0;
if (root == null) return deep;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
deep++;
while(size-- > 0){
TreeNode node = q.poll();
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
if(node.left == null && node.right == null){
return deep;
}
}
}
return deep;
}
}
填充每个节点的下一个右侧节点指针
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-8zq5rRPo-1687871390519)(https://note.youdao.com/yws/res/89088/WEBRESOURCE00b2aeebc2719bb5e1e5fe53763d4720)]
思路
本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
代码
class Solution {
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> q = new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
int size = q.size();
Node node, nodePre = null;
for(int i = 0; i < size; i++){
if(i == 0){
// 获取第一层的头节点
nodePre = q.poll();
node = nodePre;
}else{
node = q.poll();
nodePre.next = node;
nodePre = nodePre.next;
}
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
}
return root;
}
}
填充每个节点的下一个右侧节点指针II
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/
题目
这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道
思路
代码
class Solution {
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> q = new LinkedList<>();
q.offer(root);
Node node = null, nodePre = null;
while(!q.isEmpty()){
int size = q.size();
for(int i = 0; i < size; i++){
if(i == 0){
// 取出一层的头结点
nodePre = q.poll();
node = nodePre;
}else{
node = q.poll();
nodePre.next = node;
nodePre = nodePre.next;
}
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
}
return root;
}
}
翻转二叉树
题目
翻转一棵二叉树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrWrXXUh-1687871390519)(https://note.youdao.com/yws/res/89100/WEBRESOURCE8a4964b4818a67946a2a30d16abdd2de)]
思路
前后序遍历都可以
代码
迭代(BFS)
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
int size = q.size();
while(size-- > 0){
TreeNode node = q.poll();
swap(node);
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
}
return root;
}
public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
递归(DFS)
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
swap(root);
invertTree(root.left);
invertTree(root.right);
return root;
}
public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
拓展
递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。
如果一定要使用递归中序的方式写,也可以
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
invertTree(root.left);
swap(root);
invertTree(root.left); // 这里还是左孩子,因为中间翻转了
return root;
}
public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
对称二叉树
题目
给定一个二叉树,检查它是否是镜像对称的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRVlitvk-1687871390520)(https://note.youdao.com/yws/res/89117/WEBRESOURCEd9ae2d42883e340ec48f59eb53b2fd0e)]
思路
首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7H8HfVLv-1687871390520)(https://note.youdao.com/yws/res/89122/WEBRESOURCE5062719eb32c78b08150fe1907c68008)]
代码
递归
递归三部曲
1、确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
返回值自然是bool类型。
2、确定终止条件
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
- 左右都不为空,比较节点数值,不相同就return false
3、确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return compare(root.left, root.right);
}
// 递归返回值
public boolean compare(TreeNode leftNode, TreeNode rightNode){
// 递归终止条件
if(leftNode == null && rightNode != null) return false;
else if(leftNode != null && rightNode == null) return false;
else if(leftNode == null && rightNode == null) return true;
else if(leftNode.val != rightNode.val) return false;
// 下一层
boolean outSide = compare(leftNode.left, rightNode.right);
boolean inSide = compare(leftNode.right, rightNode.left);
boolean isSame = outSide && inSide;
return isSame;
}
}
迭代
使用队列
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root.left);
q.offer(root.right);
while(!q.isEmpty()){
TreeNode leftNode = q.poll();
TreeNode rightNode = q.poll();
// 如果两个都为空则相同
if(leftNode == null && rightNode == null){
continue;
}
if(leftNode == null && rightNode != null) return false;
else if(leftNode != null && rightNode == null) return false;
else if(leftNode.val != rightNode.val) return false;
q.offer(leftNode.left); // 将左子树 左节点加入队列
q.offer(rightNode.right); // 将右子树 右节点加入队列
q.offer(leftNode.right); // 将左子树 右节点加入队列
q.offer(rightNode.left); // 将右子树 左节点加入队列
}
return true;
}
}