二叉树学习刷题总结

基础导图(23.2.22)

最近开始学二叉树,做了上面一个简单的导图,也刷了一点点二叉树相关的题,自己给自己做个总结,同时后面每天晚上加班结束后刷个几道题,记录下思路,边学边记录。

深度遍历

144. 二叉树的前序遍历(23.2.27)

这里前序、中序、后序的递归解法都是一个模板,只是 result.add(root.val) 的位置不同,还是很容易理解此递归的,别的递归对我来说很难理解。

同时这里给出了 迭代 法,在后面的 层序遍历 中会类似采用 队列 的方式实现 BFS。

前序:根-左-右

递归:

首先判断 root 是不是 null,如果为 null 直接return即可,没意义。

如果不为 null,此时 root 就是我们说的整个二叉树的根节点,那么我们就将他的值放到List集合中

【 result.add(root.val) 】,接着按照前序遍历顺序找根节点的左孩子,判断左孩子下面是否还有孩子,因此这种重复操作便可以用到递归,还调用自身方法,把左孩子放进去【preorder(root.left,result)】,

这样一步操作就可以把总的根节点 root 它的左孩子 root.left 下面全部进行一个遍历,同理 就是右孩子

【preorder(root.right,result)】,因此就是先把根节点的值放进去,接着递归左孩子、右孩子。最终达到全部遍历完成。

中序、后序遍历的递归方法可以说一模一样,不同的是把 result.add(root.val) 和preorder(root.left,result)、preorder(root.right,result) 顺序按照对应的遍历方式调整顺序即可。

迭代:

前序的迭代法也很容易理解。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;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Sean_Asu/article/details/129392045