代码随想录训练营D15-二叉树篇 p2 | 层序遍历 10 、226.翻转二叉树、101.对称二叉树 2

(一)102. 二叉树的层序遍历

题目/文章/视频链接
102. 二叉树的层序遍历

1.思路

栈是先进后出,所以迭代法使用栈来代替底层的递归;树的递归相当于图的深度优先搜索遍历。
而层次遍历,一层一层由内而外,与队列的先进先出是相符合的。树的层次遍历相当于图的广度优先搜索。

在这里插入图片描述

题目的关键是,如何确定哪些结点是一层的。
通过一个变量 cnt来记录每层的结点数。

首先,根结点6入队。
开始外层循环,条件是队列不为空。

更新cnt = queue.size,现在是1
开始内层while循环,负责把当前层结点都出队(出队同时数值记录到数组),并且其孩子们都入队,循环条件是cnt>0(只有cnt>0才是当前层)

直到循环结束,返回数组

2.代码

//每层结点的数值放在一个list中,再将整体结果放在一个list中
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if(root == null){
        return result;
    }

    //创建队列,根结点入队
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while(!queue.isEmpty()){
        int cnt = queue.size();
        //创建当前层的数组
        List<Integer> curLevel = new ArrayList<>();
        while(cnt-- > 0){//将当前层都出队(出队同时加入数组),并且将孩子们入队
            TreeNode node = queue.poll();
            curLevel.add(node.val);

            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
        }
        //把每层结点的list放入result list
        result.add(curLevel);
    }
    return result;
}

题目/文章/视频链接

(二)层序遍历 9 道题目

题目/文章/视频链接

[No.i] 107. 二叉树的层序遍历 II

力扣链接🔗

1. 思路

1.俺の思路
仍旧按照上一题正常的层序遍历的思路,将每一层的元素存到一个数组中,再将这些数组存到一个大数组中。然后再将大数组中的数组们reverse一下。
(给牛完了,开始在汉语里面夹英语了)

2.代码随想录
可以将result表创建为LinkedList类型的,这样在插入时可以使用头插法

LinkedList<List<Integer>> result = new LinkedList<>();//左侧类型一定是LinkedList
result.addFirst(node);//使用头插法插入结点

2. 代码

public List<List<Integer>> levelOrderBottom(TreeNode root) {
     List<List<Integer>> result = new ArrayList<>();
     if(root == null){
         return result;
     }

     //创建队列
     Deque<TreeNode> queue = new ArrayDeque();
     queue.add(root);

     while(!queue.isEmpty()){
         //记录当前层要出队结点的数量cnt;每层都要创建数组,记录当前层的结点
         int cnt = queue.size();
         List<Integer> curList = new ArrayList<>();
         while(cnt-- > 0){
             //当前层结点出队,数值存入当前层数组;将左右孩子入队
             TreeNode node = queue.poll();
             curList.add(node.val);

             if(node.left != null){
                 queue.add(node.left);
             }
             if(node.right != null){
                 queue.add(node.right);
             }
         }
         //一层的遍历结束,将当前层数组放入总数组中
         result.add(curList);
     }

     Collections.reverse(result);
     return result;
 }

[No.ii] 199. 二叉树的右视图

力扣链接🔗

1. 思路

1.俺の思路
按前面层序遍历的思路,每层循环开始前使用变量cnt记录了队列中的结点个数(即当前层的结点数),因此本题中可以使当前层一直出队,直到当前层只剩一个结点,将该结点放入result list中。

2. 代码

public List<Integer> rightSideView(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    //先判空
    if(root == null){
        return result;
    }

    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);

    while(!queue.isEmpty()){
        int cnt = queue.size();
        //开始每层的循环
        while(cnt-- > 0){
            //属于当前层的结点出队,若有左右孩子也依次入队
            TreeNode node = queue.poll();
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
            //直到当前层结点数为1时,才存入result数组,由于我实在while循环中提前--了cnt
            //所以是cnt为0时,就将结点存入result中
            if(cnt == 0){
                result.add(node.val);
            }
        }

    }
    return result;
}

[No.iii] 637. 二叉树的层平均值

力扣链接🔗

1. 思路

1.俺の思路
由题意,即计算每一层的平均值,再将平均值放入result数组中。

层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。

算法:
创建result数组,并判空输入的根结点root,若root为null,直接返回result。创建辅助队列queue,将根结点入队
进入外层while循环,进行条件是队列不为空;
进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。在内层while中,队头结点出队,若有左右孩子,则左右孩子依次入队,将当前结点的值累加到sum。
在内层while结束后,计算平均值。

2. 代码

public List<Double> averageOfLevels(TreeNode root) {
    List<Double> result = new ArrayList<>();
    if(root == null){
        return result;
    }

    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while(!queue.isEmpty()){
        int cnt = queue.size();
        int num = cnt;
        double sum = 0;
        //开始当前层的一系列操作
        while(cnt-- > 0){
            //队头结点出队,若有左右结点,则左右结点入队
            TreeNode node = queue.poll();
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
            sum += node.val;
        }
        result.add(sum/num);
    }
    return result;
}

[No.iv] 429. N 叉树的层序遍历

力扣链接🔗

1. 思路

1.俺の思路
仍旧是遍历树,将结点放入队列中。

层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。

算法:
1.创建result数组,并判空输入的根结点root,若root为null,直接返回result。
2.创建辅助队列queue,将根结点入队。
3.进入外层while循环,进行条件是队列不为空;
4.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
在内层while中,首先创建一个list,用于存当前层的结点。队头结点出队,若孩子不为null,则一直遍历孩子结点的list,并将孩子结点入队。
内层while结束后,将当前层的数组add进result数组中。

2. 代码

public List<List<Integer>> levelOrder(Node root) {
    List<List<Integer>> result = new ArrayList<>();
    if(root == null){
        return result;
    }

    Deque<Node> queue = new ArrayDeque<>();
    queue.add(root);
    while(!queue.isEmpty()){
        //创建当前层的数组,并先记好当前层的结点数
        int cnt = queue.size();
        List<Integer>  curList = new ArrayList<>();
        while(cnt-- > 0){
            Node node = queue.poll();
            if(!node.children.isEmpty()){
                List<Node> children = node.children;
                for(Node child : children){
                    queue.add(child);
                }
            }
            curList.add(node.val);
        }
        result.add(curList);
    }
    return result;
}

[No.v] 515. 在每个树行中找最大值

力扣链接🔗

1. 思路

1.俺の思路
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。

算法:
1.创建result数组,并判空输入的根结点root,若root为null,直接返回result。
2.创建辅助队列queue,将根结点入队。
3.进入外层while循环,进行条件是队列不为空;
4.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
进入while前,设置一个变量记录当前层结点的最大值curMax,设置变量cnt存下当前层的结点个数。
在内层while中,队头结点出队,若左右孩子不为null,则左右孩子结点入队。
内层while结束后,将当前层最大值进result数组中。

2. 代码

public List<Integer> largestValues(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if(root == null){
        return result;
    }

    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while (!queue.isEmpty()){
        int curMax = Integer.MIN_VALUE;
        int cnt = queue.size();
        while(cnt-- > 0){
            TreeNode node = queue.poll();
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
            //更新当前层的max值
            curMax = node.val > curMax ? node.val : curMax;
        }
        result.add(curMax);
    }
    return result;
}

[No.vi] 116. 填充每个节点的下一个右侧节点指针

力扣链接🔗

1. 思路

1.俺の思路
层序遍历并能区分每层的思路:
1.借用队列。因为队列是先进先出,符合层序遍历的顺序
2.使用一个变量cnt记录每层结点的个数,以此来控制可以刚好将同一层的结点从队列中输出。

算法:
1.创建辅助队列queue,将根结点入队。
2.进入外层while循环,进行条件是队列不为空;
3.进入内层while,每一层(层是指 层次遍历的层)的循环,进行条件是cnt>0,以此来保证在while中操作的都是当前层的结点。
进入while前,先出队一个结点,记为前继结点frontNode。设置变量cnt存下当前层的结点个数。
在内层while中,若frontNode左右孩子不为null,则左右孩子结点入队。若当前层剩余结点数为0,即cnt==0,则frontNode为当前层最后一个结点,将其next指为null,break;开启下一层的循环。若当前层还有结点curNode,则出队,frontNode指向curNode,更新frontNode = curNode
4.最后返回root

2. 代码

public Node connect(Node root) {
    if(root == null){
        return root;
    }
    Deque<Node> queue = new ArrayDeque<>();
    queue.add(root);
    while(!queue.isEmpty()){
        Node frontNode = queue.poll();
        int cnt = queue.size();
        //开始每层的操作
        while(cnt >= 0){
            if(frontNode.left != null){
                queue.add(frontNode.left);
            }
            if(frontNode.right != null){
                queue.add(frontNode.right);
            }

            //每层最后一个结点的next指向null
            if(cnt == 0){
                frontNode.next = null;
                break;
            }else {
                Node curNode = queue.poll();
                frontNode.next = curNode;
                frontNode = curNode;
                --cnt;
            }
        }
    }
    return root;

}

[No.vii] 117. 填充每个节点的下一个右侧节点指针 II

力扣链接🔗

1. 思路

0.与上一题的区别。
上一题是完全二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。
这一题是二叉树,所以不一定有两个节点

代码一样滴

[No.viii] 104. 二叉树的最大深度

力扣链接🔗

1. 思路

按第一题模版例题的思路,只不过再内层循环不需要做什么操作,只是在内层循环结束时,++depth;
在外层循环结束时,返回depth

2. 代码

public int maxDepth(TreeNode root) {
    if(root == null){
        return 0;
    }
    int maxDepth = 0;

    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);

    while (!queue.isEmpty()){
        int cnt = queue.size();
        while(cnt-- > 0){
            TreeNode node = queue.poll();
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
        }
        //一层结束
        ++maxDepth;
    }
    return maxDepth;
}

[No.viii] 111. 二叉树的最小深度

力扣链接🔗

1. 思路

按第一题模版例题的思路,只不过再内层循环不需要做什么操作,只是在内层循环结束时,++depth;并且若有结点左右孩子都为null,就return;
以及在外层循环结束时,return depth。

2. 代码

public int minDepth(TreeNode root) {
    if(root == null){
        return 0;
    }
    int min = 1;

    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while(!queue.isEmpty()){
        int cnt = queue.size();
        while(cnt-- > 0){
            TreeNode node = queue.poll();
            if(node.left == null && node.right == null){
                return min;
            }
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
        }
        ++min;
    }
    return min;
}

(三) 226.翻转二叉树 (优先掌握递归)

这道题目 一些做过的同学 理解的也不够深入,建议大家先看我的视频讲解,无论做过没做过,都会有很大收获。

题目/文章/视频链接

1. 思路

1.递归:推荐前序、后序
在这里插入图片描述

发现只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果。
因为可以拆分为相同的细小的都是左右孩子翻转的这个动作,所以可以使用递归。
前序和后序思路较顺;中序也可以但有点绕。(以下连续的数字为层序遍历)

中序:遍历顺序,左中右。走到1,return到2,2的两个孩子翻转,来到4。4的两个孩子翻转,此时2为根的小树来到了右侧,根据中序遍历的顺序,左根右,现在又要调换右侧根为2的小树了。此时根为2的小树,其左右孩子被调换两次了;根为7的小树的左右孩子就没被调换过。所以,有点绕的是,遍历递归代码要写为左中左了。因为经历’中’之后,左右的位置已经互换了,要想找‘右’,就得找‘左’。

前序:根左右。4先调换,此时4 72 6913。调换7,此时4 72 9613。再走到7的两个孩子69,他们是叶子结点不用调换孩子return。调换2,此时4 72 9631。再2的两个叶子结点,没有孩子,最终return。
后序:从叶子结点依次调换到根结点4.

2.复习递归三部曲
1)确定递归函数的参数返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。

返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*。

2)确定终止条件
当前节点为空的时候,就返回 if (root == NULL) return root;

3)确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。

2. 代码

public TreeNode invertTree(TreeNode root) {
    if(root == null){
        return root;
    }
    //前序遍历(先根结点交换左右孩子,再操作(反转)左右子树)
    swapNode(root);
    invertTree(root.left);
    invertTree(root.right);
    return root;
}
	private void swapNode(TreeNode node){
	    TreeNode temp = node.left;
	    node.left = node.right;
	    node.right = temp;
}

3. 实现过程中的问题

这样交换的是指向两个孩子结点的指针,而没有交换两个孩子本身。

public TreeNode invertTree(TreeNode root) {
    if(root == null){
        return root;
    }
    swapNode1(root.left, root.right);
    invertTree(root.left);
    invertTree(root.right);
    return root;
}
	private void swapNode1(TreeNode left, TreeNode right){
	    TreeNode temp = left;
	    left = right;
	    right = temp;
}

(四)101. 对称二叉树 (优先掌握递归)

先看视频讲解,会更容易一些。

题目/文章/视频链接

1. 思路

在这里插入图片描述
如上图所示,是一棵对称二叉树。那么这道题的子问题是考虑,某结点的左右孩子是否相等吗?对于结点1是这样的,但是对于结点2,发现他的孩子3,4并不相等。
所以,本题实际考察的是根结点的两个子树翻转后是否相等。即左子树的外侧是否与右子树的里侧相等。

递归三部曲
1)确定递归函数的参数和返回值
由于是要判断左右两棵子树是否对称,所以参数是左右两个子树的根结点。由于是判断是否对称,所以返回值是boolean类型。

2)确定终止条件
!注意我们比较的其实不是左孩子和右孩子,比较的是在对称位置上的点。所以如下我称之为左节点右节点。

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚。以此来避免出现空指针的情况。

  • 左为空,右不为空。不相等,返回false
  • 右为空,左不为空。不相等,返回false
  • 左右都为空,相等。返回true

两节点都不为空时。

  • 两结点的值不相等,返回false
  • 两结点的值相等,返回true

3)确定单层递归的逻辑
只有左右两结点都不为空,且数值相等才会进入单层递归的逻辑。

比较二叉树外侧是否对称(调递归):传入的是左节点的左孩子,右节点的右孩子。//左
比较内侧是否对称(调递归),传入左节点的右孩子,右节点的左孩子。//右
如果左右都对称就返回true ,有一侧不对称就返回false 。//中

单对于左节点来说是,左右中。所以这里也看作是后序遍历。
所以通过代码也能看出只能使用后序遍历。必须内侧,外侧都比较过后,都为true,才代表根结点是true。

2. 代码

class Solution {
    public boolean isSymmetric(TreeNode root) {
        //根据题意至少有一个树节点,所以这里不对root判空了
        return compare(root.left, root.right);
    }

    //递归
    private boolean compare(TreeNode left, TreeNode right){
        if(left == null && right == null){
            return true;
        }else if(left == null || right == null){
            return false;
        }else if(left.val != right.val){
            return false;
        }else {//只剩下两结点不为空,且值相等这种情况了
            boolean out = compare(left.left, right.right);//比较外侧是否对称
            boolean in = compare(left.right, right.left);//比较内侧是否对称
            return out && in;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值