**今日内容:层序遍历、 226.翻转二叉树、101.对称二叉树 **
代码随想录链接:代码随想录 (programmercarl.com)
层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
102.二叉树的层序遍历
给你二叉树的根节点
root,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
迭代层序遍历代码如下:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
List<List<Integer>> resList = new ArrayList<List<Integer>>();
if (root == null) return resList;
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = queue.size();
while(len > 0){
TreeNode cur = queue.poll();
itemList.add(cur.val);
if(cur.left != null)queue.offer(cur.left);
if(cur.right != null)queue.offer(cur.right);
len--;
}
resList.add(itemList);
}
return resList;
}
}
递归写法
递归函数的主要作用是遍历完所有节点,因此递归函数内部肯定需要调用两次原函数,一次是左孩子另一次是右孩子。
递归函数返回的条件是当前节点非空,返回上一个节点。
但是从上一个节点的视角来看,不过只是一行代码执行结束,也就是说上一个节点也只是被遍历了一次。
既然每个节点只遍历一次,那么我们需要在递归函数内读取val。
为了让每个值都在正确的层上,递归函数的传入参数为节点和层数(深度),每一层通过层数将节点的值添加到List当中去。
递归层序遍历代码如下:
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
levelCheck(root,0);
return resList;
}
public void levelCheck(TreeNode node, Integer deep){
if(node == null)return;
deep++;
if(resList.size() < deep){
List<Integer> item = new ArrayList<Integer>();
resList.add(item);
}
resList.get(deep-1).add(node.val);
levelCheck(node.left, deep);
levelCheck(node.right, deep);
}
}
107.二叉树的层序遍历||
给你二叉树的根节点
root,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
相较于上一题,这题是一个反向的遍历,但是每一层之间的顺序不变,那么对上一题结果进行一个翻转即可。
199.二叉树的右视图
给定一个二叉树的 根节点
root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
层序遍历,然后每一层只取最右边那个,递归不太好知道最右边那个,用迭代的话,修改一下add就好了把。
代码如下:
class Solution {
public List<Integer> rightSideView(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
List<Integer> resList = new ArrayList<Integer>();
if (root == null) return resList;
queue.offer(root);
while(!queue.isEmpty()){
int len = queue.size();
for(int i = 0;i < len; i++){
TreeNode cur = queue.poll();
if(cur.left != null)queue.offer(cur.left);
if(cur.right != null)queue.offer(cur.right);
if(i == len - 1){
resList.add(cur.val);
}
}
}
return resList;
}
}
637.二叉树的层平均值
给定一个非空二叉树的根节点
root, 以数组的形式返回每一层节点的平均值。与实际答案相差10-5以内的答案可以被接受。
层序遍历然后每一层求和。
就是加一个double类型的sum,添加的时候用(sum/len)即可。代码略。
429.N叉数的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
N叉数对子节点的访问通过节点的列表,那么加一个循环访问子节点即可?
代码如下:
class Solution {
public List<List<Integer>> levelOrder(Node root) {
Queue<Node> queue = new LinkedList<Node>();
List<List<Integer>> resList = new ArrayList<List<Integer>>();
if (root == null) return resList;
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> itemList = new ArrayList<Integer>();
int len = queue.size();
for(int i = 0; i < len; i++){
Node cur = queue.poll();
itemList.add(cur.val);
List<Node> children = cur.children;
if (children == null || children.size() == 0) {
continue;
}
for (Node child : children) {
if (child != null) {
queue.offer(child);
}
}
}
resList.add(itemList);
}
return resList;
}
}
这里用List<Node> children = cur.children;来调用子节点。是为了下面方便写。
515.在每个树行中找最大值
给定一棵二叉树的根节点
root,请找出该二叉树中每一层的最大值。
层序后每层找一个最大值添加进去。
116.(117.)填充每个节点的下一个右侧节点指针(||)
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为
NULL。
初始状态下,所有 next 指针都被设置为NULL。
先放一个层序遍历,在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
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++){
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;
}
}
104.二叉树的最大深度
给定一个二叉树
root,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
层序迭代的时候记录一下深度。
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);
len--;
}
depth++;
}
return depth;
}
}
111.二叉树的最小深度
给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。
在上面那个加个限定条件,两个子节点不存在的时候,结束。
if(node.left == null && node.right == null){return depth + 1;
226.翻转二叉树
给你一棵二叉树的根节点
root,翻转这棵二叉树,并返回其根节点。
如果刚刚层序遍历做魔怔了,那么这题就会层序遍历然后翻转列表。 这题弄一个中间节点,每一个节点的子节点转换一下就好。用哪种遍历都可以。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return null;
}
invertTree(root.left);
invertTree(root.right);
invert(root);
return root;
}
private void invert(TreeNode root){
TreeNode temp = new TreeNode();
temp = root.left;
root.left = root.right;
root.right = temp;
}
}
101.对称二叉树
给你一个二叉树的根节点
root, 检查它是否轴对称。
递归法
递归三部曲
- 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
返回值自然是bool类型。
代码如下:
bool compare(TreeNode* left, TreeNode* right)
- 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
- 左右都不为空,比较节点数值,不相同就return false
此时左右节点不为空,且数值也不相同的情况我们也处理了。
代码如下:
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false; // 注意这里我没有使用else
注意上面最后一种情况,我没有使用else,而是else if, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
- 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
代码如下:
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。
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;
}
}
层序遍历十道题目不想偷懒,画了一些时间都看了一下具体的,然后最后一个过的有点草率,下次复习看到这里要去重点掌握以下。