Summary of binary tree learning brush questions

Basic Mapping (23.2.22)

Recently, I started to learn binary trees. I made the simple map above and did some questions related to binary trees. I made a summary for myself. At the same time, I wrote down a few questions after working overtime every night, recorded my thoughts, and learned as I went. side record.

depth traversal

144. Preorder traversal of a binary tree (23.2.27)

Here, the recursive solutions of preorder, inorder and postorder are all templates, but the position of result.add(root.val) is different, so it is easy to understand this recursion. Other recursions are difficult for me to understand.

At the same time, the iterative method is given here, and BFS will be implemented in a similar way to queues in the subsequent layer order traversal.

Preorder: root-left-right

recursion:

First judge whether the root is null. If it is null, just return directly, which is meaningless.

If it is not null, then root is what we call the root node of the entire binary tree, then we put its value in the List collection

[ result.add(root.val) ], then find the left child of the root node according to the preorder traversal order, and judge whether there are children under the left child, so this repeated operation can use recursion, and call its own method, put Put the left child in [preorder(root.left,result)],

This one-step operation can perform a traversal of the total root node root and its left child root.left, and the right child in the same way

[preorder(root.right,result)], so the value of the root node is put in first, and then the left child and the right child are recursive. Finally, all traversals are completed.

The recursive methods of in-order and post-order traversal can be said to be exactly the same, the difference is to adjust the order of result.add(root.val) and preorder(root.left,result), preorder(root.right,result) according to the corresponding traversal method Just order.

Iterate:

前序的迭代法也很容易理解。new 一个 List 放值 和 root 的空判断 算是每次的固定,这里我们借助栈来实现前序遍历操作,栈是先进后出,而我们前序最终出来的结果是:根-左-右,因此我们先把根结点 root 压入栈,然后while判断栈是否为空,不为空就新定义一个结点 node 用来接收 root 结点位置,然后弹出栈中栈顶元素,第一次时也就是把root弹出,将它的值放入集合里,接着判断它的右孩子是不是为空,不为空就压入栈,再判断左孩子,同理不为空压入。一旦遍历结束,此时叶子结点下面没有孩子,那么栈也就为空,就结束while循环,最终返回 list 即可。

(按照先进后出的性质,我们就要先把右压入栈,再压入左,这样保证出栈的是 左-右)

//递归
// class Solution {
//     public List<Integer> preorderTraversal(TreeNode root) {
//         List<Integer> result = new ArrayList<Integer>();
//         preorder(root,result);
//         return result;
//     }

//     public void preorder(TreeNode root,List<Integer> result){
//         if(root == null) return;
//         result.add(root.val);
//         preorder(root.left,result);
//         preorder(root.right,result);
//     }
// }

//迭代
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        if(root == null) return result;
        Stack<TreeNode> stack = new Stack<>();  // new 一个栈
        stack.push(root);                          // 把根结点入栈
        while (!stack.isEmpty()){                  // 只要栈里不为空
            TreeNode node = stack.pop();        // 先把根结点弹出,每次走完循环都会把根节点下面根节点弹出
            result.add(node.val);               // 然后把根节点的值添加到list集合中
            if(node.right != null) stack.push(node.right);  //判断根节点右边是不是空,如果不为空入栈
            if(node.left != null) stack.push(node.left);    //判断根节点左边是不是空,如果不为空入栈
        }
    //前序遍历:根左右,根据栈的规则,根节点入栈弹出,然后得先入右节点再入左节点,这样弹出顺序就是根-左-右
    return result;
    }
}

94. 二叉树的中序遍历(23.2.27)

中序:左-根-右

迭代:由于中序是 左-根-右,那么我们就先判断左边是否为null,不为null就压入,然后判断左孩子下面是否还有叶子节点,同理还先判断左孩子下面的左孩子是否为null,为null就回到左孩子位置,判断左孩子的右孩子是否为null,以此往返。

用上图配合下面代码来解释:此时1结点也就是cur结点,然后 cur != null,进入while循环,cur != null,将1结点压入栈,然后往左走(cur = cur.left),此时cur的左孩子为null,那么cur 也就为null,cur = null 的话 就将cur = stack.pop(),也就是 cur 又再次回到了1结点的位置,然后先将 栈顶元素弹出(1),将 他的值放入 List,也就是把1放进去,接着令 cur = cur.right,此时右孩子结点不为null,值为2,那么再次走 if 判断,将 结点2压入栈,再次令 cur = cur.left,此时 cur 到了结点3的位置,再次走 if 判断,又将 结点3压入栈,然后令 cur = cur.lef,此时cur = null,走else判断,此时栈中有2 ,3,然后再令cur = 3的位置,并将3弹出放入list中,再令 cur = cur.right,此时cur = null,还走else判断,此时栈中只有2,然后令cur = 2的位置,将2弹出并且放到list中,再令cur = 2的右孩子,此时cur = null,并且栈stack = null,不满足while条件,也就跳出循环。那么此时 list 中有【1、3、2】,返回list即可。

class Solution {
    // //递归
    // public List<Integer> inorderTraversal(TreeNode root) {
    //     List<Integer> result = new ArrayList<Integer>();
    //     inorder(root,result);
    //     return result;
    // }

    // public void inorder(TreeNode root,List<Integer> result){
    //     if(root == null) return; 
    //     inorder(root.left, result);
    //     result.add(root.val);
    //     inorder(root.right, result);
    // }

    //迭代
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        if(root ==null) return result;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()){
            if(cur != null){
                stack.push(cur);
                cur = cur.left;
            }else{
                cur = stack.pop();
                result.add(cur.val);
                cur = cur.right;
            }
        }
        return result;
    }
}

145. 二叉树的后序遍历(23.2.27)

后序:左-右-根

迭代:这里用了前序一个技巧。

前序是:根-左-右,插入的时候先插右,再插左

而后序是:左-右-根,因此可以按先序写法,写成:根-右-左,然后反转就成了:左-右-根。

那么 根-右-左,就是插入的时候先插入左,再插入右就可以实现。

最终reverse即可。

class Solution {
    
    // //递归
    // public List<Integer> postorderTraversal(TreeNode root) {
    //     List<Integer> result = new ArrayList<Integer>();
    //     postorder(root,result);
    //     return result;
    // }

    // public void postorder(TreeNode root,List<Integer> result){
    //     if(root == null) return; 
    //     postorder(root.left, result);
    //     postorder(root.right, result);
    //     result.add(root.val);
    // }

    //迭代
    // 后序:左-右-根,而先序是:根-左-右 ,因此可以按先序写法,写成:根-右-左,然后反转就成了:左-右-根
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if(root == null) return result;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if(node.left != null) stack.push(node.left);
            if(node.right != null) stack.push(node.right);
        }
        Collections.reverse(result);
        return result;
    }
}

广度遍历

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

层序遍历这里,递归我已经不是特别明白了。。。所以我一直采用的BFS,借助队列来实现,也可以说有了自己的一个固定模板。每次依据题意不同,来改变内层循环里面的条件。

首先根据返回类型,定义一个相同的List,然后判断 root == null,为空就直接renturn list即可。

接着定义一个 TreeNode 类型的队列,然后将 root 压入队列。

while循环,条件判断 队列不为空,如果是双层List,那么再定义一个单层List用来接收里面每一层的值,同时取一下 队列的长度 int len = que.size();

接着可以用一个for循环判断,也可以用 while(len >0)来循环判断,定义一个结点tmpNode来接收队列弹出的队顶元素的位置,也就是root,并将它的值放到单层list中,然后判断tmpNode的左孩子是否为空,不为空就压入队列,再判断右孩子是否为空,不为空就压入队列,循环结束将list 添加到 双层 list中,最终返回即可。

class Solution {
    // BFS--迭代方式--借助队列
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> resList = new ArrayList<List<Integer>>();
        if(root == null) return resList;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);
        while(!que.isEmpty()){
            List<Integer> list = new ArrayList<Integer>();
            int len = que.size();

            while(len > 0){
                TreeNode tmpNode = que.poll();
                list.add(tmpNode.val);

                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
            resList.add(list);
        }
        return resList;
    } 
}

107. 二叉树的层序遍历 II(23.3.6)

这里让我们返回其节点值 自底向上的层序遍历,刚好与上面层序遍历结果相反,这样的话我们在原有层序遍历基础上修改一下即可,每层循环结束的时候都要把该层的值放到list集合中,因为默认add是从前往后添加,那么我们改成让他每次添加该层值到双层list的第一个即可,这样就保证了我们第一层的值在最后一个,最后一层的值添加进去在第一个。

ret.add(0,resList)
class Solution {
        public List<List<Integer>> levelOrderBottom(TreeNode root) {
            List<List<Integer>> ret = new ArrayList<List<Integer>>();
            if(root == null) return ret;
            Queue<TreeNode> que = new LinkedList<TreeNode>();
            que.offer(root);

            while(!que.isEmpty()){
                List<Integer> resList = new ArrayList<Integer>();
                int len = que.size();
                while(len >0) {
                    TreeNode tmpNode = que.poll();
                    resList.add(tmpNode.val);
                    if (tmpNode.left != null) que.offer(tmpNode.left);
                    if (tmpNode.right != null) que.offer(tmpNode.right);
                    len--;
                }
                ret.add(0,resList); //每次在ret的首部加入resList,这样就可以保证最终出来的结果:最早进来的在最后,最后进来的在头部
            }
            return ret;
        }
    }

199. 二叉树的右视图(23.3.6)

该题只要右孩子,因此我们在原有层序遍历上进行修改即可,我们只将每一层最后一个孩子加入list即可。

这里判断就是,每次结束len--,当len=0的时候,说明此时队列里面已经没有元素了,而此时取到的结点就是该层最后一个结点,那么将它放到list即可。

class Solution {
        public List<Integer> rightSideView(TreeNode root) {
            List<Integer> list = new ArrayList<Integer>();
            if(root == null) return list;
            Queue<TreeNode> que = new LinkedList<TreeNode>();
            que.offer(root);

            while(!que.isEmpty()){
                int len = que.size();
                while(len >0) {
                    TreeNode tmpNode = que.poll(); 
                    if(tmpNode.left != null) que.offer(tmpNode.left);
                    if(tmpNode.right != null) que.offer(tmpNode.right);
                    len--;
                    if(len == 0) list.add(tmpNode.val); //只有上一层的最后一个才能加入list,如果右面有,就是右面;右面没有,左面就是上一层的最后一个               
                }
            }
            return list;
        }
    }

637. 二叉树的层平均值(23.3.6)

这题要求返回每一层值的平均值,那么我们就要定义一个sum = 0来每层循环时统计每一层结点的值,然后最终该层结束时将 sum / len 也就是该层的平均值存入list即可。

class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> list = new ArrayList<Double>();
        if(root == null) return list;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);

        while(!que.isEmpty()){
            double sum = 0;
            int len = que.size();
            for(int i =0;i < len; i++){
                TreeNode tmpNode = que.poll();
                sum += tmpNode.val;
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
            }
            list.add(sum / len);
        }
        return list;
    }
}

429. N 叉树的层序遍历(23.3.7)

这题不再是二叉树的层序遍历,而是N叉树,但是操作和二叉树没太大区别,二叉树里面我们是判断他的左孩子右孩子是否为空,然后加入队列,但是N叉树 不止2个孩子,可能3 4 个孩子,因此我们就要用for循环遍历,将它的孩子值全加入到list中。

for(Node child : tmpNode.children){
que.offer(child);
}
class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> resList = new ArrayList<List<Integer>>();
        if(root == null) return resList;
        Queue<Node> que = new LinkedList<Node>();
        que.offer(root);

        while(!que.isEmpty()){
            int len = que.size();
            List<Integer> list = new ArrayList<Integer>();

            while(len >0){
                Node tmpNode = que.poll();
                list.add(tmpNode.val);
                for(Node child : tmpNode.children){
                    que.offer(child);
                }
                len--;
            }
            resList.add(list);
        }
        return resList;
    }
}

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

该题是找出该二叉树中每一层的最大值,因此我们需要在内层循环加入一个判断,判断每一层的值大小,将最大的加入到list中即可。

这里用了一个 Integer.MIN_VALUE 和 Math的max方法。

先定义maxVal为Integer.MIN_VALUE,保证它的最小性,然后在内层循环,每一层判断时将maxVal 等于该层最大的节点值。最终只需要将每一层的maxVal 放到 list中即可。

int maxVal = Integer.MIN_VALUE;
maxVal = Math.max(maxVal, tmpNode.val);
class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> resList = new ArrayList<Integer>();
        if(root == null) return resList;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);

        while(!que.isEmpty()){
            int len = que.size();
            int maxVal = Integer.MIN_VALUE;
            while(len >0){  
                TreeNode tmpNode = que.poll();
                maxVal = Math.max(maxVal, tmpNode.val);
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
            resList.add(maxVal);
        }
        return resList;
    }
}

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

本题略为不同,但是大差不差,要求是每层结束后跟个null,同时接着下层首个元素。

由于是满二叉树,那么每层节点数就是该层数,这里用的for循环,由于i从0开始,也就是每层最后一个元素,要让最后一个元素指向null,因此 i < len -1,也就是i在该层最后一个之前,全部相互连接,i在该层最后一个时,令tmpNode.next = que.peek(),此时que是空的,所以que.peek() 也就是null,让每层最后一个指向null,再指向下一层的第一个。最终返回 root即可。

class Solution {
    public Node connect(Node root) {
        if(root == null) return root;
        Queue<Node> que = new LinkedList<Node>(); 
        que.add(root);

        while(!que.isEmpty()){
            int len = que.size();
            for(int i =0;i <len; i++){
                Node tmpNode = que.poll();
                //由于是满二叉树,那么每层节点数就是该层数,由于i从0开始,也就是每层最后一个元素是i=len-1
                //保证每一层的最后一个元素节点下一层的首个元素
                if(i < len -1) tmpNode.next = que.peek(); 
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
            } 
        }
        return root;
    }
}

上面是用for循环的用法,由于是满二叉树,所以可以直接用

if(i < len -1) tmpNode.next = que.peek();

当然如果不是满二叉树,就可以用下面这个通用方法:

class Solution {
    public Node connect(Node root) {
        if(root == null) return root;
        Queue<Node> que = new LinkedList<Node>(); 
        que.add(root);

        while(!que.isEmpty()){
            int len = que.size();
            Node pre = null;
            while(len >0){
                Node tmpNode = que.poll();
                //由于是满二叉树,那么每层节点数就是该层数,由于i从0开始,也就是每层最后一个元素是i=len-1
                //保证每一层的最后一个元素节点指向下一层的首个元素
                if(pre != null)  pre.next = tmpNode;
                pre = tmpNode;
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len --;
            } 
        }
        return root;
    }
}

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

这题与116的区别就在于:116的树是满二叉树,而本题的数不是。所以我们可以直接用116题的下面的通用解法。

同时接116题的通用题解:要想保证它从左结点直接指向右节点,就需要借助一个工具节点,这里定义一个 pre 节点为 null,在第一层时,此时pre 仍然为null,那么让 pre = 节点 1的位置,然后遍历第二层,此时 tmpNode = 2,第二层第一个(2)时 pre 不再为 null,那么让 pre.next = tmpNode,也就是让第一层尾节点(1)指向第二层头节点(2),然后再让 pre = tmpNode,也就是 此时pre = 2,继续遍历第二层最后一个元素(3),此时 tmpNode = 3,那么让 pre 指向 tmpNode,也就是由2指向3,进行连接,以此往复。

class Solution {
    public Node connect(Node root) {
        Queue<Node> que = new LinkedList<Node>();
        if(root == null) return root;
        que.offer(root);

        while(!que.isEmpty()){
            int len = que.size();
            Node pre = null;
            while(len >0){
                Node tmpNode = que.poll();
                //如果pre为空就表示node节点是这一行的第一个
                //如果没有前一个节点指向他,那么就让前一个节点指向他
                if(pre != null ) pre.next = tmpNode;
                pre = tmpNode;
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
        }
        return root;
    }
}

当然这样的效率其实并不高,如图:

看完题解的一个大佬的优化,将它改成链表的形式,琢磨了许久,看了评论才大致明白。

题解:这里关键的2步就是下面的

cur = cur.next;
cur = dumy.next;

以题图为例讲解:首先cur = 1,然后 cur.left 不为空,也就是2,那么让 pre由空指向2,接着pre = 2,同理cur.right 不为空,也就是3,那么让 pre由2指向3,然后cur = cur.next,因为此时第一层cur只有一个1,那么经过cur = cur.next之后,cur = null,本层while结束,执行 cur = dumy.next; ,这样cur就由第一层到了第二层,此时cur = 2,再次进入内层while循环,然后经过2个if之后,4与5进行了连接(4--->5),然后 cur = cur.next ,那么cur就从2变到了3的位置,再经过if之后,将4、5、7进行了连接,接着本层while结束,cur = 4,2个if无效,然后cur从4到7走完即可,结束,这样就将整个链表进行了连接。

    public Node connect(Node root) {
        if(root == null) return root;
        Node cur = root;
        while(cur != null){
            Node dumy = new Node(0); //dumy 为每一层的头虚拟节点
            Node pre = dumy;
            while(cur != null){
                if(cur.left != null){  //左节点不为空,就将下一层的pre指向左孩子,然后pre = pre.next
                    pre.next = cur.left;
                    pre = pre.next;
                }
                if(cur.right != null){ //右节点不为空,就接着将pre指向右孩子,然后pre = pre.next
                    pre.next = cur.right;
                    pre = pre.next;
                }
                //上面两个if保证了每一层左节点/右节点他们自己之前的相连
                cur = cur.next; //然后 将cur从左边到右边,这样保证 左节点与右节点进行连接
            }
            cur = dumy.next; //这个的作用就是 连接完一层之后,将cur放到下一层,保证上一层的尾与当前层的头连接
        }
        return root;
    }
}

同时可以看出效率明显的提高了

104. 二叉树的最大深度(23.3.9)

本题其实很简单,直接在层序基础上改动即可,因为要的是深度,而我们层序遍历时最终返回的List集合长度就是它的深度,但是需要注意的是,List集合里还有个List,包含了null值,所以我们就不能用原有的返回外层list长度的方法思路。更为方便的直接定义一个size = 0,然后每层循环结束令size+1即可,最终返回size也就是他的深度。

class Solution {
    //BFS
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);
        int size = 0;
        while(!que.isEmpty()){
            int len = que.size();
            
            while(len >0){
                TreeNode tmpNode = que.poll();
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
            size++;
        }
        return size;
    }
}

但是很明显,用广度遍历来遍历深度,效率并不高,我们可以采用递归方法。

class Solution {
    //DFS
    //二叉树的最大深度是左子树和右子树深度最大值+1(根),也就是深度优先
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }else{
            int leftHeight = maxDepth(root.left);
            int rightHeight = maxDepth(root.right);
            return Math.max(leftHeight,rightHeight)+1;
        }
    }
}

111. 二叉树的最小深度(23.3.9)

求最小深度,我们可以分2 种情况:

第一种:碰到左右孩子均为空的,此时直接返回即可,这个深度就说他的最小深度

第二种:叶子节点同层,缺少单个左右节点或者满节点,此时最小深度就是当前深度

那么依据此思路实现代码,遇到第一种的时候直接返回即可,遇到第二种就像往常遍历一样走完全部,返回深度即可。

注意:以往我们depth++会写在内层循环结束之后,但是这样的话在下面判断 左右孩子均为空 的情况的时候,我们 直接返回了 depth,此时的 depth 还是上一层的深度,并不是当前层的深度,因此把 depth++ 放到内层循环上面,这样保证是当前层。

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);
        int depth = 0;

        while(!que.isEmpty()){
            int len = que.size(); 
            depth++;   
            while(len >0){
                TreeNode tmpNode = que.poll();
                //左右孩子均为空,此时就是最小深度,直接返回即可
                if(tmpNode.left == null && tmpNode.right == null) return depth;
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
        }
        return depth;
    }
}

当然也可以用递归,我只列举我能理解的简单的递归:

这里需要注意的是:

1、104题 讲过的深度=高度+1。

2、如果左、右有一个为空,那么空的这个节点说明他就是叶子节点,高度必定为0,因此我们需要返回高度大的进行+1,所以这里 leftHeight + rightHeight + 1 ,本质上就一个 leftHeight+1 或者 rightHeight + 1 。(高度,高度不是深度!高度为0不代表深度为0!)

3、如果都不为空,那么我们最后返回遍历过的左右较小的高度+1即可。

    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        int leftHeight = minDepth(root.left);
        int rightHeight = minDepth(root.right);
        //如果左右孩子有一个为空的情况,必定有一个为0,那么返回深度大的深度:左高度+右高度+1
        //如果都不为空,返回较小高度+1
        return root.left == null || root.right == null ? leftHeight + rightHeight + 1 : Math.min(leftHeight,rightHeight) +1
    }
}

236. 翻转二叉树(23.3.9)

本题翻转二叉树,由于我经常用BFS,所以这里仍然以BFS写法为主。

思路:每层遍历时,将每一层的节点进行swap替换,然后进入下一层,再替换,以此类推,那么我们就仍然可以在原有层序遍历基础上进行简单修改即可:添加一个swap方法。

这里自己定义了一个替换方法:

public void swap(TreeNode root) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}

在原有层序遍历基础上直接加上swap()即可。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root == null) return root;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root);

        while(!que.isEmpty()){
            int len = que.size();
            while(len >0){
                TreeNode tmpNode = que.poll();
                swap(tmpNode);
                if(tmpNode.left != null) que.offer(tmpNode.left);
                if(tmpNode.right != null) que.offer(tmpNode.right);
                len--;
            }
        }
        return root;
    }

    public void swap(TreeNode root) {
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
    }
}

589. N 叉树的前序遍历(23.3.10)

前面提到过N叉的层序遍历,遍历他的children,这里同样不例外,这个前序、后序遍历,和前面的二叉树的前、后遍历相似,用到了stack,需要注意的是,由于是前序(跟-左-右),所以我们应当先把跟进栈、右边进栈,然后进左边,这样保证最终出来的是跟-左-右,于是对于N叉的孩子,我们需要写一个for循环,让他从孩子的最右边开始进栈,然后往左缩小,就是下面这个for循环。

for(int i =tmpNode.children.size()-1; i>=0; i--){
stack.push(tmpNode.children.get(i));
}

迭代法:

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> resList = new ArrayList<Integer>();
        if(root == null) return resList;
        Stack<Node> stack = new Stack<Node>();
        stack.push(root);

        while(!stack.isEmpty()){
            int len = stack.size();
            Node tmpNode = stack.pop();
            resList.add(tmpNode.val);
            for(int i =tmpNode.children.size()-1; i>=0; i--){
                stack.push(tmpNode.children.get(i));
            }
        }
        return resList;
    }
}

很明显这里效率不高,递归更适合,同样在原本二叉树的基础上修改:

前序(根-左-右),先把root.val进去,然后再遍历孩子。

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> resList = new ArrayList<>();
        helper(root, resList);
        return resList;
    }
    public void helper(Node root, List<Integer> resList) {
        if (root == null) {
            return;
        }
        resList.add(root.val);
        for (Node tmpNode : root.children) {
            helper(tmpNode, resList);
        }
    }
    
}

590. N 叉树的后序遍历(23.3.10)

迭代:

前序(根-左-右),后序(左-右-根)

按照前序的逻辑先进跟,再进右,再进左(出栈顺序:根-左-右),而后序在前序基础上,先进跟,再左,再进右(出栈顺序:跟-右-左),反转即可变成:左-右-跟。所以这里内层for就不需要按照前序的来改了,直接按照层序遍历的即可,最终reverse。

for (Node item : tmpNode.children) {
stack.push(item);
}
class Solution {
    //迭代
    public List<Integer> postorder(Node root) {
        List<Integer> resList = new ArrayList<>();
        if(root == null) return resList;
        Stack<Node> stack = new Stack<Node>();
        stack.push(root);

        while(!stack.isEmpty()){
            Node tmpNode = stack.pop();
            resList.add(tmpNode.val);
            for (Node item : tmpNode.children) {
                stack.push(item);
            }
        }
        Collections.reverse(resList);
        return resList;
    }
}

递归:

后序(左-右-根),先遍历孩子,然后再把root.val进去。

class Solution {
    //递归
    public List<Integer> postorder(Node root) {
        List<Integer> resList = new ArrayList<>();
        helper(root, resList);
        return resList;
    }

    public void helper(Node root, List<Integer> resList) {
        if (root == null) {
            return;
        }
        for (Node tmpNode : root.children) {
            helper(tmpNode, resList);
        }
        resList.add(root.val);
    }
}

101. 对称二叉树(23.3.13)

本题与前面的翻转二叉树有点相似,看对称不对称,比较的是左右孩子的最外侧、最内侧是否相同,基于此,我们就可以考虑,首先加入队列的时候,把root的左右孩子都加进去,然后进行判断,如果左右都为空,那就没意义,结束整个循环即可;如果左右有一个为空或者都不为空但是值不相等,那么就说明不是对称二叉树,返回false即可,最后都符合了,那么剩下的就是往各自的孩子遍历再比较。重点就是比较这里,我们定义了一个leftNode,一个rightNode,分别代表两边孩子,那么我们加入队列的时候,首先把leftNode的左孩子加入,然后加入rightNode的右孩子,这样两个位置就是相对称的,然后再加入leftNode的右孩子,加入rightNode的左孩子,然后再进行比较。

普通队列:

class Solution {
    // 普通队列
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return false;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(root.left);
        que.offer(root.right);

        while(!que.isEmpty()){
            TreeNode leftNode = que.poll();
            TreeNode rightNode = que.poll();
            if (leftNode == null && rightNode == null) {
                continue;
            }
            if(leftNode == null || rightNode == null || leftNode.val != rightNode.val){
                return false;
            }
            que.offer(leftNode.left);
            que.offer(rightNode.right);
            que.offer(leftNode.right);
            que.offer(rightNode.left);

        }
        return true;
    }
}

其实除了普通队列,还可以用双端队列,顾名思义,两边都可以进出那么加入跟取出就是再原本offer、poll的基础上加上First,Last,那么双端队列加入的时候,先从左侧(offerFirst)加入左孩子,然后从(offerLast)加入右孩子,然后进行比较,几乎和普通队列一样,只有最后都满足条件时加入队列的顺序不一样,首先从左侧加入左-左孩子,然后再加入左-右孩子,然后从右侧加入右-右孩子,然后再加入右-左孩子,这样也是位置对称的,不理解的话画下图就很清晰了。

双端队列:

class Solution {
    // 双端队列
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return false;
        Deque<TreeNode> que = new LinkedList<TreeNode>();
        que.offerFirst(root.left);
        que.offerLast(root.right);

        while(!que.isEmpty()){
            TreeNode leftNode = que.pollFirst();
            TreeNode rightNode = que.pollLast();
            if (leftNode == null && rightNode == null) {
                continue;
            }
            if(leftNode == null || rightNode == null || leftNode.val != rightNode.val){
                return false;
            }
            que.offerFirst(leftNode.left);
            que.offerFirst(leftNode.right);
            que.offerLast(rightNode.right);
            que.offerLast(rightNode.left);

        }
        return true;
    }
}

100. 相同的树(23.3.13)

和对称二叉树几乎一样,不一样的是,这是比较2个树,但是解法基本一致,在最后加入队列的时候,按顺序依次加入:p-左孩子,q-左孩子;p-右孩子,q-右孩子。


class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) return true;
        if(p == null || q == null) return false;
        Queue<TreeNode> que = new LinkedList<TreeNode>();
        que.offer(p);
        que.offer(q);

        while(!que.isEmpty()){
            TreeNode pNode = que.poll();
            TreeNode qNode = que.poll();
            if(pNode == null && qNode == null) continue;
            if(pNode == null || qNode == null || pNode.val != qNode.val) return false;
            que.offer(pNode.left);
            que.offer(qNode.left);
            que.offer(pNode.right);
            que.offer(qNode.right);
        }
        return true;
    }
}

110. 平衡二叉树(23.3.14)

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1

这题直接采用递归,先递归求出来左、右孩子的高度,然后进行比较,如果左右孩子高度任意一个为 -1,那么说明不是平衡二叉树,如果左右高度差的绝对值>1,说明也不是二叉树,直接false,除此之外说明符合条件,那么返回左右孩子最大的高度+1,就是其最大高度。

注意:上面说深度= 高度 +1,而本题求得是高度差,那么按往常得话不该+1,但是力扣这里所谓得高度,其实就是从该节点到叶子节点的层数,而本题的该节点,是根节点,那么就相当于是深度,(深度:根节点到叶子节点的层数),因此需要+1。

class Solution {
    public boolean isBalanced(TreeNode root) {
        return getheight(root) != -1;
    }
    public int getheight(TreeNode root){
        if(root == null) return 0;
        int leftHeight = getheight(root.left);
        int rightHeight = getheight(root.right);
        if(leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) >1){
            return -1;
        }else{
            return Math.max(leftHeight,rightHeight) +1;
        }
    }
}

Guess you like

Origin blog.csdn.net/Sean_Asu/article/details/129392045