Day16-第六章 二叉树 part02-层序遍历,反转二叉树,对称二叉树

在进行二叉树层序遍历之前,首先对二叉树的深度优先遍历回顾一下。以前序遍历为例,看看能不能有助于层序遍历代码的理解和记忆。

1.首先是递归

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        preorder(root,ans);
        return ans;
    }
    private void preorder(TreeNode root,List<Integer> ans){
        if(root==null){
            return;
        }
        ans.add(root.val);
        preorder(root.left,ans);
        preorder(root.right,ans);
    }
}

2.然后是比较简单容易理解的迭代

public class Blog {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if(root==null){
            return ans;
        }
        Deque<TreeNode> stack = new ArrayDeque<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode pop = stack.pop();
            ans.add(pop.val);
            if(pop.right!=null){
                stack.push(pop.right);
            }
            if(pop.left!= null){
                stack.push(pop.left);
            }
        }
        return ans;
    }
}

3.统一迭代

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
      List<Integer> ans = new ArrayList<>();
        if(root==null){
            return ans;
        }
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode dummy= new TreeNode(-1);
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode pop = stack.pop();
            if(pop!=dummy){ //Arraydeque里不能push null;
                stack.push(pop);//后序遍历
                stack.push(dummy);//后序遍历
                if(pop.right!=null){
                    stack.push(pop.right);
                }
                stack.push(pop);//中序遍历
                stack.push(dummy);//中序遍历
                if (pop.left!=null){
                    stack.push(pop.left);
                }
                stack.push(pop);//前序遍历
                stack.push(dummy);//前序遍历
            }else {
                pop = stack.pop(); 
                ans.add(pop.val);
            }
        }
        return ans;
    }
}

层序遍历:

与前中后序遍历的不同:

1.与前中后序遍历不同的是,因为层序遍历要把一层的节点全给处理掉再进行下一层,所以需要两个循环来保证一层的节点都被处理了。比如前序遍历,虽然一开始将右节点压入,但直到左边节点压入弹出完了才开始处理右边节点。层序遍历的内层循环避免了这一点。

2.层序遍历用队列先进先出,而前中后序遍历则是用栈的逻辑后进先出。

chatgpt:

  1. 层序遍历 (BFS) 的特性

    • 你想从根节点开始,然后访问它的所有直接子节点,再访问这些子节点的子节点,依此类推。这意味着你先访问接近根的节点,然后逐渐远离根节点。
    • 因此,你需要一个能够按入队顺序保存节点的数据结构。这样,当你从队列中取出一个节点并添加它的子节点时,你能确保这些子节点会在所有其他更深层次的节点之前被访问。这就是先进先出 (FIFO) 的逻辑。
  2. 前序、中序、后序遍历 (DFS) 的特性

    • 当你访问一个节点时,你想立即访问它的子节点,然后再访问它的兄弟节点。这意味着你会尽可能深入地遍历每个分支,直到达到一个叶子节点,然后再回溯。
    • 当你访问一个节点并移动到它的子节点时,你需要有一个方式来“记住”这个节点,这样在你完成子节点的遍历后,你可以回到它并继续访问其他子节点。这就需要一个能够回溯到最后访问的节点的数据结构。这就是后进先出 (LIFO) 的逻辑。

简单地说,不同的遍历方法对应着不同的遍历需求,而每种数据结构(队列和栈)提供了满足这些需求的特定功能。

  • 对于BFS,你需要按照你访问它们的顺序访问节点,所以使用先进先出的队列是合适的。

  • 对于DFS,你需要能够回到最近访问的节点并探索它的其他路径,所以使用后进先出的栈是合适的。

下面是代码,代码中外层循环中的templist的初始化和总结对层序遍历过程并不重要。外层需要初始化什么应该随题目灵活改变。其实就是一个que.size(),加上内层不断poll和添加左右孩子。

poll的是要处理的本层,添加的左右孩子是要处理的下一层。

public class Leetcode102 {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        Deque<TreeNode> que = new LinkedList();//层序遍历用队列来模拟
        que.offer(root);
        while (!que.isEmpty()) {//意味着处理完所有节点。这里是层的循环
            List<Integer> templist = new ArrayList<>();//因为要一行一行的输出,所以要在外层循环内归零,归零一次输出完一行
            int n = que.size();//每一层需要循环的次数,取决于内层循环向对列里加了几个left,right节点。正好为下一层节点数
            while (n > 0) {//这里是层内循环
                TreeNode tempnode = que.poll();
                templist.add(tempnode.val);
                if (tempnode.left != null) {
                    que.offer(tempnode.left);
                }
                if (tempnode.right != null) {
                    que.offer(tempnode.right);
                }//两个if语句将新的左右节点加入对列中
                n--;//将节点数依次poll,即外层que.size那一句
            }
            ans.add(templist);//归零前记录
        }
        return ans;
    }
}

107. 二叉树的层序遍历 II

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if(root==null){
            return ans;
        }
        Deque<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while (!que.isEmpty()){
            List<Integer> level = new ArrayList<>();
            int n = que.size();
            while (n>0){
                TreeNode poll = que.poll();
                level.add(poll.val);
                if(poll.left!=null) que.offer(poll.left);
                if(poll.right!=null) que.offer(poll.right);
                n--;
            }
            ans.add(0,level);
        }
        return ans;
    }
}

199. 二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

本题几个总结:

1.右视图看到的不等于右孩子

2.因为不需要每一层答案按层输出,所以不用在层的循环开始前重新定义一个用于储存层答案的列表。直接加入到最终答案中即可。

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
         List<Integer> ans = new ArrayList<>();
       if(root==null){
           return ans;
       }
       Deque<TreeNode> que = new LinkedList<>();
       que.offer(root);
       while (!que.isEmpty()){
           int n = que.size();
           while (n>0){
               TreeNode poll = que.poll();
               if(n==1){
                   ans.add(poll.val);
               }
               if(poll.left!=null) que.offer(poll.left);
               if(poll.right!=null) que.offer(poll.right);
               n--;
           }
       }
       return ans;
    }
}

637. 二叉树的层平均值

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

其实写到这一题慢慢有了感觉哪里是层内要做的哪里是层外要做的分清楚就好

要么记录下n的值

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
           List<Double> ans = new ArrayList<>();
        Deque<TreeNode> que = new LinkedList<>();
        if (root == null) {
            return ans;
        }
        que.offer(root);
        while (!que.isEmpty()) {
            Double count = 0.0;
            int n = que.size();
            int temp = n;
            while (n > 0) {
                TreeNode tempnode = que.poll();
                count = count + tempnode.val;
                if (tempnode.left != null) que.offer(tempnode.left);
                if (tempnode.right != null) que.offer(tempnode.right);
                n--;
            }
            ans.add(count / temp);
        }
        return ans;
    }
}

要么使用for循环

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> ans= new ArrayList<>();
        if(root==null){
            return ans;
        }
        Deque<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while (!que.isEmpty()){
            Double count = 0.0;
            int n = que.size();
            for (int i = 0; i < n; i++) {
                TreeNode poll = que.poll();
                count=count+poll.val;
                if(poll.left!=null) que.offer(poll.left);
                if(poll.right!=null) que.offer(poll.right);
            }
            ans.add(count/n);
        }
        return ans;
    }
}

429. N 叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> ans = new ArrayList<>();
        Deque<Node> que = new LinkedList<>();
        if(root==null){
            return ans;
        }
        que.offer(root);
        while(!que.isEmpty()){
            List<Integer> templist = new ArrayList<>();
            int n = que.size();
            for (int i = 0; i < n; i++) {
                Node tempnode = que.poll();
                templist.add(tempnode.val);
                for(Node child:tempnode.children){
                    if(child!=null);
                    que.offer(child);
                }
            }
            ans.add(templist);
        }
        return ans;
    }
}

515. 在每个树行中找最大值

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        Deque<TreeNode> que = new LinkedList();
        que.offer(root);
        while (!que.isEmpty()){
            int n = que.size();
            int max = Integer.MIN_VALUE;
            while (n>0){
                TreeNode poll = que.poll();
                max = Math.max(max,poll.val);
                if(poll.left!=null) que.offer(poll.left);
                if(poll.right!=null) que.offer(poll.right);
                n--;
            }
            ans.add(max);
        }
        return ans;
    }
}

116. 填充每个节点的下一个右侧节点指针

定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

有几个需要注意的点和感悟:

1.相当于一个双指针,所以第一个点要单独拿出来。当然也可以写在内层循环中(第二个代码)

2.要处理的本层只和poll出来的有关,offer的都是下一层要处理的

3.外层while循环里的内容更像是对一层内容的初始化和总结

class Solution {
    public Node connect(Node root) {
        if(root==null){
            return null;
        }
        Deque<Node> que = new LinkedList<>();
        que.offer(root);
        while (!que.isEmpty()){
            Node cur = que.poll();
            int n = que.size();
            if (cur.left != null) que.offer(cur.left);
            if (cur.right != null) que.offer(cur.right);//这四句是处理每一层第一个节点
            while (n>0){//处理每一层剩下的节点
                Node next = que.poll();
                if (next.left != null) que.offer(next.left);
                if (next.right != null) que.offer(next.right);
                cur.next = next;
                cur =next;
                n--;
            }
        }
        return root;
    }
}

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

其实解法和116一样,不贴了。

给定一个二叉树:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL 。

初始状态下,所有 next 指针都被设置为 NULL 。

示例 1:

输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。

示例 2:

输入:root = []
输出:[]

提示:

  • 树中的节点数在范围 [0, 6000] 内
  • -100 <= Node.val <= 100

进阶:

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序的隐式栈空间不计入额外空间复杂度。

226. 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

示例 2:

输入:root = [2,1,3]
输出:[2,3,1]

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目范围在 [0, 100] 内
  • -100 <= Node.val <= 100
class Solution {
    public TreeNode invertTree(TreeNode root) {
        reverse(root);
        return root;
    }
    private void reverse(TreeNode root){
        if(root ==null){
            return;
        }
        TreeNode temp = root.left;
        root.left=root.right;
        root.right= temp;
        reverse(root.left);
        reverse(root.right);
    }
}

迭代的代码如下,可以看到两个循环贴在一起,并没有要初始化的东西。 

lass Solution {
    public TreeNode invertTree(TreeNode root) {
       if(root == null){
            return null;
        }
        Deque<TreeNode> que = new ArrayDeque<>();
        que.offer(root);
        while (!que.isEmpty()){
            for (int i = 0; i < que.size(); i++) {
                TreeNode poll = que.poll();
                TreeNode temp = poll.left;
                poll.left = poll.right;
                poll.right = temp;
                if(poll.left!=null) que.offer(poll.left);
                if(poll.right!=null) que.offer(poll.right);//left和right可以互换
            }
        }
        return root;
    }
}

101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

class Solution {
    public boolean isSymmetric(TreeNode root) {
         return check(root.left, root.right);
    }

    private boolean check(TreeNode left, TreeNode right) {
        if (left == null && right == null) {
            return true;
        }
        if (left == null || right == null) {
            return false;
        }
        if (left.val != right.val) {
            return false;
        } 
            return check(left.left, right.right) && check(left.right, right.left); 
    }
}
public boolean isSymmetric2(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offerFirst(root.left);
        deque.offerLast(root.right);
        while (!deque.isEmpty()) {
            TreeNode leftNode = deque.pollFirst();
            TreeNode rightNode = deque.pollLast();
            if (leftNode == null && rightNode == null) {
                continue;
            }
//            if (leftNode == null && rightNode != null) {
//                return false;
//            }
//            if (leftNode != null && rightNode == null) {
//                return false;
//            }
//            if (leftNode.val != rightNode.val) {
//                return false;
//            }
            // 以上三个判断条件合并
            if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
                return false;
            }
            deque.offerFirst(leftNode.left);
            deque.offerFirst(leftNode.right);
            deque.offerLast(rightNode.right);
            deque.offerLast(rightNode.left);
        }
        return true;
    }
public boolean isSymmetric3(TreeNode root) {
        Queue<TreeNode> deque = new LinkedList<>();
        deque.offer(root.left);
        deque.offer(root.right);
        while (!deque.isEmpty()) {
            TreeNode leftNode = deque.poll();
            TreeNode rightNode = deque.poll();
            if (leftNode == null && rightNode == null) {
                continue;
            }
//            if (leftNode == null && rightNode != null) {
//                return false;
//            }
//            if (leftNode != null && rightNode == null) {
//                return false;
//            }
//            if (leftNode.val != rightNode.val) {
//                return false;
//            }
            // 以上三个判断条件合并
            if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
                return false;
            }
            // 这里顺序与使用Deque不同
            deque.offer(leftNode.left);
            deque.offer(rightNode.right);
            deque.offer(leftNode.right);
            deque.offer(rightNode.left);
        }
        return true;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值