[Day15]二叉树

80 阅读8分钟

**今日内容:层序遍历、 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 , 检查它是否轴对称。

递归法

递归三部曲

  1. 确定递归函数的参数和返回值

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

返回值自然是bool类型。

代码如下:

bool compare(TreeNode* left, TreeNode* right)
  1. 确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点

  • 左节点为空,右节点不为空,不对称,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, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。

  1. 确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回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;
    }
}

层序遍历十道题目不想偷懒,画了一些时间都看了一下具体的,然后最后一个过的有点草率,下次复习看到这里要去重点掌握以下。