【LC】二叉树应用强化OJ

news2024/11/18 19:39:48

在这里插入图片描述

博客主页: 心荣~
系列专栏:【LeetCode/牛客刷题】
一句短话: 难在坚持,贵在坚持,成在坚持!

文章目录

  • 1. 检查两颗树是否相同
  • 2. 另一颗树的子树
  • 3. 二叉树最大深度
  • 4. 判断—颗二叉树是否是平衡二叉树
  • 5. 对称二叉树
  • 6. 二叉树的构建及遍历
  • 7. 二叉树的分层遍历
  • 8. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先
  • 9. 二叉搜索树转换成排序双向链表
  • 10. 根据一棵树的前序遍历与中序遍历构造二叉树.
  • 11. 根据─棵树的中序遍历与后序遍历构造二叉树
  • 12. 二叉树创建字符串
  • 13. 二叉树前序非递归遍历实现
  • 14. 二叉树中序非递归遍历实现
  • 15. 二叉树后序非递归遍历实现

1. 检查两颗树是否相同

在线OJ:100. 相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1

img

输入:p = [1,2,3], q = [1,2,3]
输出:true

示例 2

img

输入:p = [1,2], q = [1,null,2]
输出:false

示例 3

img

输入:p = [1,2,1], q = [1,1,2]
输出:false

提示

  • 两棵树上的节点数目都在范围 [0, 100] 内
  • -104 <= Node.val <= 104

💯解题思路:

  1. 如果两棵树的根结点都为空,则两棵树相同。
  2. 如果两棵树的根结点有一个为空,则两棵树必然不相同。
  3. 如果两棵树都不为空,则判断根结点的值是否相同,不相同则这两棵树必然不相同。
  4. 如果两棵树根结点的值相同,则需要判断两棵树的左右子树是否相同,如果相同则这两棵树相同。
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) {// 1
            return true;
        }
        if(p == null && q != null || p != null && q == null) {// 2
            return false;
        }
        if(p.val != q.val) { // 3
            return false;
        }
        // 4
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

2. 另一颗树的子树

在线OJ:572. 另一棵树的子树

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true;否则,返回 false

二叉树 tree 的一棵子树包括tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例 1

img

输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true

示例 2

img

输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false

提示

  • root 树上的节点数量范围是 [1, 2000]
  • subRoot 树上的节点数量范围是 [1, 1000]
  • -104 <= root.val <= 104
  • -104 <= subRoot.val <= 104

💯解题思路:

判断一棵树是否为另一棵树的子树我们可以基于判断两棵树是否相同去做。

  1. 如果root与subRoot有一棵树是空,那么subRoot必然不是root的子树。
  2. 如果root与subRoot相同,那么subRoot肯定是root的子树。
  3. 如果root的子树含有与subRoot相同的树,那么subRoot肯定是root的子树。
class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null && subRoot != null || root != null && subRoot == null) {
            return false; // 1
        }
        if(isSameTree(root, subRoot)) {
            return true; // 2
        }
        if(isSubtree(root.left, subRoot)) { // 3
            return true;
        }
        if(isSubtree(root.right, subRoot)) {
            return true;
        }
        return false;
    }
    //判断两棵树是否相同
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null) {
            return true;
        }
        if(p == null && q != null || p != null && q == null) {
            return false;
        }
        if(p.val != q.val) {
            return false;
        }
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

3. 二叉树最大深度

在线OJ:104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例

给定二叉树 [3,9,20,null,null,15,7]3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3

💯解题思路:

  1. 如果根结点为空,则这棵树的高度为0,返回0。
  2. 一棵二叉树的最深深度即为左右子树深度的最大值加上1。
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) { //1
            return 0;
        }
        int leftHight = maxDepth(root.left);
        int rightHight = maxDepth(root.right); //2
        return leftHight>rightHight ? leftHight+1 : rightHight+1;
    }
}

4. 判断—颗二叉树是否是平衡二叉树

在线OJ:110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1

示例 1

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2

img

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

示例 3

输入:root = []
输出:true

提示

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

💯解题思路:

这道题可以基于求二叉树的高度来做,

  1. 如果树为空,则这棵树是平衡二叉树。
  2. 获取该树左右子树的高度差的绝对值,如果大于1,则这棵树肯定不是平衡二叉树。
  3. 如果高度差绝对值不大于1,再判断该树的左右子树是否都是平衡二叉树,如果是,则这一整棵树是平衡二叉树。
class Solution {
    //写法二:写法一优化
    //如果有子树不满足平衡二叉树, 就返回树的高度为-1
    //与写法一相比,避免了重复递归子树
    public boolean isBalanced(TreeNode root) {
        if(root == null) { 
            return true;
        }
        return maxDepth(root) >= 0;
    }
    public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHight = maxDepth(root.left);
        int rightHight = maxDepth(root.right);
        if(leftHight >= 0 && rightHight >=  0 && 
           Math.abs(leftHight - rightHight) <= 1) {
            return Math.max(leftHight, rightHight) + 1;
        }else {
            return -1;
        }
    }

    //写法一:
    /*public boolean isBalanced(TreeNode root) {
        if(root == null) { // 1
            return true;
        }
        int leftHight = maxDepth(root.left);   
        int rightHight = maxDepth(root.right);
        
        return Math.abs(leftHight-rightHight) < 2   // 2
            && isBalanced(root.left) 
            && isBalanced(root.right); // 3
    }
    //求二叉树的高度
    public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHight = maxDepth(root.left);
        int rightHight = maxDepth(root.right);
        return leftHight>rightHight ? leftHight+1 : rightHight+1;
    }
    */
}

5. 对称二叉树

在线OJ:101. 对称二叉树

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

示例 1

img

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

示例 2

img

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

提示

  • 树中节点数目在范围 [1, 1000] 内
  • -100 <= Node.val <= 100

💯解题思路:

  1. 如果这棵树为空树, 那么这棵树是平衡二叉树,返回true
  2. 如果不为空, 那么开始递归判断;
  • 递归过程:

(1)判断左右子树两个根节点值是否相等

(2)判断 A(左) 的右子树与 B(右) 的左子树是否对称(递归)

(3)判断 A(左) 的左子树与 B 的右子树是否对称(递归)

  • 递归结束条件:

(1)左右子树两个根节点值不相等

(2)都为空指针则返回 true

(3)只有一个为空则返回 false

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) {// 1
            return true;
        }
        return isSymmtericChild(root.left, root.right); // 2
    }
    // 2 
    private boolean isSymmtericChild(TreeNode leftTree, TreeNode rightTree) {
        if(leftTree != null && rightTree == null 
           || leftTree == null && rightTree != null) {
            return false;
        }
        if(leftTree == null && rightTree == null) {
            return true;
        }
        if(leftTree.val != rightTree.val) {
            return false;
        }
        return isSymmtericChild(leftTree.left, rightTree.right) 
            && isSymmtericChild(leftTree.right, rightTree.left);
    }
}

6. 二叉树的构建及遍历

在线OJ:KY11 二叉树遍历

编一个程序,读入用户输入的一串先序遍历字符串根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

输入描述

输入包括1行字符串,长度不超过100。

输出描述

可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行。

示例:

输入:abc##de#g##f### 
输出:c b e g d f a 

💯解题思路:

  1. 要注意到题目需要多组输入;
  2. 先构建二叉树结点, 包含char类型的数值域, 左右孩子节点的引用;
  3. 遍历字符串,根据前序遍历的顺序构建二叉树; 遇到非#符号,创建一个结点并存入目标值,然后按照前序遍历的顺序构建该树的左子树,右子树,遇到#符号,相当于遇到二叉树的空,将字符串下标加1返回即可。
  4. 使用一个成员变量,对字符串进行遍历,因为在递归的过程中,设置局部变量会被销毁 ; 而且每构建一个二叉树(遍历完一个字符串)应将该成员变量再次设置为0。
  5. 中序遍历输出已经创建好的二叉树。
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    static class TreeNode { // 2
        public char val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(char val) {
            this.val = val;
        }
    }
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 1
           String str = in.nextLine();
           Main mai = new Main();
           TreeNode root = mai.createTree(str); // 3

           mai.inOrder(root);
        }
    }
    //构建二叉树
    public int i;//遍历字符串的下标  // 4
    public TreeNode createTree(String str) { // 3
        TreeNode root = null;
        if(str.charAt(i) != '#') {
            root = new TreeNode(str.charAt(i));
            i++;
            root.left = createTree(str);
            root.right = createTree(str);
        }else {
            i++;
        }
        return root;
    }
    //中序遍历
    public void inOrder(TreeNode root) { // 5
        if(root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val+" ");
        inOrder(root.right);
    }
}

7. 二叉树的分层遍历

在线OJ:102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:

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

示例 3:

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

提示:

  • 树中节点数目在范围 [0, 2000] 内
  • -1000 <= Node.val <= 1000

💯解题思路:

实现层序遍历,我们可以设计一个队列来实现, 首先将根节点入队,然后循环每次将队头元素出队(出队时获取到节点中的元素)同时将出队节点的左右孩子结点(不包括空结点)依次入队,以此类推,直到最终队列和当前结点均为空时,表示遍历结束。

看题目中的输出示例, 让我们返回的是一个列表, 列表中的每个元素还是一个列表(存放每一层的数据); 也就是说我们需要将每层的元素分开; 那么这里用顺序表来实现再合适不过, list是要返回的表, tmp表中放一层中的数据, 每次遍历完一层就将tmp添加到list中。

我们可以在每次获取出队元素前,先获取队列中元素个数,这个元素个数就是当前层次的元素个数,这样就能统计每层的数据, 将每层的数据分隔开来。

  1. 记录当前队列元素个数,即二叉树每层的元素个数。
  2. 将此层二叉树的结点的左右非空孩子结点存于队列中,并将该层二叉树结点的值存于顺序表中。
  3. 将非空的子节点存入队列中。
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null) {
            return list;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while(!queue.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            int size = queue.size(); // 1

            while(size != 0) {
                size--;
                TreeNode cur = queue.poll(); // 2
                tmp.add(cur.val);
                // 3 
                if(cur.left != null) {
                    queue.offer(cur.left);
                }
                if(cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            list.add(tmp);
        }
        return list;
    }
}

8. 给定一个二叉树,找到该树中两个指定节点的最近公共祖先

在线OJ:236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3

示例 2

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3

输入:root = [1,2], p = 1, q = 2
输出:1

提示

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同 。
  • p != q
  • p 和 q 均存在于给定的二叉树中。

💯解题思路:

  • 思路1: 非递归实现

可以利用两个栈分别存储root->p和root->q的二叉树路径,然后将元素多的那一个栈内的元素出栈,直到与另一个栈的元素数量相等,最后两栈同时出栈并比较出栈的元素,如果相等,则相等的那个结点就是p,q的最近公共祖先,最后如果栈为空了还没有找到,p,q在root这棵树上没有公共祖先。

  1. 使用两个栈分别存储root->p,和root->q的路径。
  2. 寻找root->p, root->q的二叉树路径。
  3. 将元素数量较多的栈出栈,使其元素数量与另一个栈相等。
  4. 两栈同时出栈并比较,相等的元素结点即为最近公共结点。

关于 2. 寻找root->p和root->q路径的思路(递归):

2.1 如果二叉树为空或者目标节点为空,不存在路径。

2.2 创建一个栈,用于存放路径,先假设路径存在并把这条路径上的结点入栈,然后顺着这个路径寻找p或者q,如果没找到,将这条路径上的结点出栈,换另外一条路径寻找。

2.3 函数返回值为boolean,以判断是否找到正确的路径。

  • 思路2: 递归实现
  1. 如果为空树返回null。
  2. 如果p,q结点均与根结点root相同,则最近公共祖先为root。
  3. 如果p,q都在左子树或右子树,则最近公共祖先也在左子树或右子树; 此时p和q的最近公共祖先为p或者q, 具体看先碰到p还是q
  4. 如果p,q位于不同的左右子树上,则最近公共祖先为root。

对于3和4两点,我们可以分别去左右子树找p,q结点,如果在左右子树均找到,说明4成立,如果在左右子树中只找到一个,就说明3成立,如果在左右子树都没有找到,就说明p,q在root这棵二叉树上没有公共祖先。

class Solution {
    //方法二: 递归思路
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) {  // 1
            return null;
        }
        if(root == p || root == q) { // 2
            return root;
        }
        TreeNode leftTree = lowestCommonAncestor(root.left, p, q);  // 3 4
        TreeNode rightTree = lowestCommonAncestor(root.right, p, q);
        if(leftTree != null && rightTree != null) { // 4
            return root;
        }else if(leftTree != null) {  // 3
            return leftTree;
        }else if(rightTree != null) { // 3
            return rightTree;
        }else {
            return null;
        }
    }
    
    
    //方法一: 寻找交点的方法(非递归)
    /*public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || p == null || q == null) {
            return null;
        }
        Stack<TreeNode> stack1 = new Stack<>(); // 1
        getPath(root, p, stack1); // 2
        Stack<TreeNode> stack2 = new Stack<>(); // 1
        getPath(root, q, stack2); // 2

        int size1 = stack1.size();
        int size2 = stack2.size();


        if(size1 > size2) {
            int size = size1 - size2;
            while(size != 0) {         //3 
                size--;
                stack1.pop();
            }
        }else{
            int size = size2- size1;
            while(size != 0) {
                size--;
                stack2.pop();
            }
        }

        while(stack1.peek() != stack2.peek()){ // 4
            stack1.pop();
            stack2.pop();
        }
        return stack1.peek();
    }
    public boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
        if(root == null || node == null) { // 2.1
            return false;
        }
        stack.push(root);           //2.2
        if(root == node) {
            return true;
        }
        //查看左子树路径
        boolean flag1 = getPath(root.left, node, stack);
        if(flag1) {
            return true;
        }
        //查看右子树路径
        boolean flag2 = getPath(root.right, node, stack);
        if(flag2) {
            return true;
        }
        //该节点左右子树都没goal结点路径,将这个节点出栈
        stack.pop();
        return false; // 2.3
    }*/
}

9. 二叉搜索树转换成排序双向链表

在线OJ:JZ36 二叉搜索树与双向链表

描述:

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

img

数据范围

输入二叉树的节点数 0 \le n \le 10000≤n≤1000,二叉树中每个节点的值 0\le val \le 10000≤val≤1000
要求

空间复杂度O(1)O(1)(即在原树上操作),时间复杂度 O(n)O(n)

注意:

1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

4.你不用输出双向链表,程序会根据你的返回值自动打印输出

输入描述

二叉树的根节点

返回值描述

双向链表的其中一个头节点。

💯解题思路:

将二叉搜索树转换为一个升序排列的循环双向链表,我们知道二叉搜索树进行中序遍历得到的就是一个升序排列的序列, 所以我们可以通过中序遍历二叉树, 进而再修改引用指向, 让节点中left引用指向前驱, right指向后继 ;

  1. 使用一个引用prev用来保存中序遍历的前一个结点,使用引用head来记录二叉搜索树的最小结点,即双链表的头结点。
  2. 定义引用cur实现用递归实现中序遍历, 递归的结束条件为 cur == null。
  3. 修改cur节点内left和right引用的指向, cur.left = prev; prev.right = cur;(注意当修改第一个节点的指向的时候, prev是为空的, 所以prev.right = cur操作需要加限制条件prev != null , 也就是说如果prev不为null,说明当前遍历的节点在构造的双链表中存在前驱和后继) ; 然后再更新prev的值为cur。
  4. 最后双链表改造完成后, 我再让head引用向前移动, 直到head指向第一个节点(最小值节点), 返回head。
public class Solution {
    private TreeNode prev; // 1
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) {
            return null;
        }

        convertChild(pRootOfTree); // 2
        
        //将指针指向头节点
        TreeNode head = pRootOfTree;  // 1
        
        while(head.left != null) { // 4
            head = head.left;
        }
        return head;
    }
    //更改指针指向
    public void convertChild(TreeNode cur) { // 2
        if(cur == null) {
            return;
        }
        convertChild(cur.left); // 3
        cur.left = prev;
        if(prev != null) {
            prev.right = cur;
        }
        prev = cur;
        convertChild(cur.right);
    }
}

10. 根据一棵树的前序遍历与中序遍历构造二叉树.

在线OJ:105. 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

img

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

💯解题思路:

  1. 根据前序遍历数组元素, 创建根结点。
  2. 根据前序遍历数组找到根结点,通过该根节点取中序遍历数组确定该根节点的左右子树,该根结点左边的中序遍历序列为该结点的左子树,右边的中序遍历序列为该结点的右子树。
  3. 通过分而治之的思想,去创建该树的左子树与右子树。
  4. 假设遍历前序遍历数组的下标为pi,中序遍历数组的起始序列结点下标为ib,结束序列结点下标为ie,所创建树的根结点为ri,则创建一棵树的左子树中序遍历序列下标范围为[ib, ri-1],右子树中序遍历序列下标范围为[ri+1, ie],通过前序遍历的顺序去创建一棵二叉树的根,左子树和右子树。
class Solution {
    //遍历前序,拿到的是根节点
    private int preIndex = 0;
    
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeChild(preorder, inorder, 0, inorder.length-1);
    }
    
    private TreeNode buildTreeChild(int[] preorder, int[] inorder, int inbegin, int inend) {
        if(inbegin > inend) {
            return null;
        }
        //创建根节点
        TreeNode root = new TreeNode(preorder[preIndex]); // 1
        
        //找到中序遍历数组中根节点所在位置   // 2
        int rootIndex = findIndex(inorder, inbegin, inend, preorder[preIndex]);
        preIndex++;
        
        //根,左,右 先创建左树, 再创建右树    // 3
        root.left = buildTreeChild(preorder, inorder, inbegin, rootIndex-1);
        root.right = buildTreeChild(preorder, inorder, rootIndex+1, inend);
        return root;
    }
    // 2 
    private int findIndex(int[] inorder, int inbegin, int inend, int val) {
        for (int i = inbegin; i <= inend; i++) {
            if(inorder[i] == val) {
                return i;
            }
        }
        return -1;
    }
}

11. 根据─棵树的中序遍历与后序遍历构造二叉树

在线OJ:从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例 1:

img

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

💯解题思路:

这个与上面一个题的是同一类型的题, 只是将前序遍历变为了后续遍历,而我们知道根据后序遍历去获取根节点应该从后向前依次获取,所以该题只需相较于上面的前序遍历改变后序遍历数组遍历顺序和二叉树创建顺序即可。

  1. 根据后序遍历数组元素, 创建根结点。
  2. 根据后序遍历数组从右向左找到根结点,通过该根节点取中序遍历数组确定该根结点的左右子树,该根结点左边的中序遍历序列为该结点的左子树,右边的中序遍历序列为该结点的右子树。
  3. 通过分而治之的思想,去创建该树的右子树与左子树。
  4. 假设 从右向左 遍历后序遍历数组的下标为pi,中序遍历数组的起始序列结点下标为ib,结束序列结点下标为ie,所创建树的根结点为ri,则创建一棵树的左子树中序遍历序列下标范围为[ib, ri-1],右子树中序遍历序列下标范围为[ri+1, ie],通过后序遍历的顺序(从后往前)去创建一棵二叉树的根,右子树和左子树。
class Solution {
    //遍历后序,拿到的是根节点
    private int postIndex = 0;
    
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        postIndex = postorder.length-1;
        return buildTreeChild(postorder, inorder, 0, inorder.length-1);
    }
    
    private TreeNode buildTreeChild(int[] postorder, int[] inorder, int inbegin, int inend) {
        if(inbegin > inend) {
            return null;
        }
        //创建根节点
        TreeNode root = new TreeNode(postorder[postIndex]); // 1
        
        //找到中序遍历数组中根节点所在位置 // 2
        int rootIndex = findIndex(inorder, inbegin, inend, postorder[postIndex]);
        postIndex--;
        
        //左,右,根 先创建右树, 再创建左树 // 3
        root.right = buildTreeChild(postorder, inorder, rootIndex+1, inend);
        root.left = buildTreeChild(postorder, inorder, inbegin, rootIndex-1);
        return root;
    }
    // 2
    private int findIndex(int[] inorder, int inbegin, int inend, int val) {
        for (int i = inbegin; i <= inend; i++) {
            if(inorder[i] == val) {
                return i;
            }
        }
        return -1;
    }
}

12. 二叉树创建字符串

在线OJ:606. 根据二叉树创建字符串

给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。

空节点使用一对空括号对 “()” 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

示例 1

img

输入:root = [1,2,3,4]
输出:"1(2(4))(3)"
解释:初步转化后得到 "1(2(4)())(3()())" ,但省略所有不必要的空括号对后,字符串应该是"1(2(4))(3)"

示例 2

img

输入:root = [1,2,3,null,4]
输出:"1(2()(4))(3)"
解释:和第一个示例类似,但是无法省略第一个空括号对,否则会破坏输入与输出一一映射的关系。

提示

  • 树中节点的数目范围是 [1, 104]
  • -1000 <= Node.val <= 1000

💯解题思路:

  1. 按照前序遍历的顺序对二叉树进行遍历,遍历之前需要判断结点左右子树存在情况,根据不同情况添加不同的括号,可以使用StringBuilder对象来对字符串进行拼接。
  2. 首先在StringBuilder对象添加根结点数据,如果根结点的左子树不为空,加左括号,左子树根节点数据,每确定一棵树遍历完了, 就加上右括号; 如果左子树为空,就判断右子树是否存在,如果存在加上一对括号,不存在直接返回。
  3. 遍历完左子树后,再判断右子树是否为空,如果不为空,加左括号,右子树根节点数据,每确定一棵树遍历完了, 就加上右括号, 如果为空直接返回。
class Solution {
    StringBuilder stringBuilder = new StringBuilder(); // 1
    public String tree2str(TreeNode root) {
        if(root == null) {
            return stringBuilder.toString();
        }
        stringBuilder.append(root.val); // 2
        if(root.left != null) {
            stringBuilder.append("(");
            tree2str(root.left);
            stringBuilder.append(")");
        }else {
            if(root.right == null) {
                return stringBuilder.toString();
            }else {
                stringBuilder.append("()");
            }
        }

        if(root.right != null) {  // 3
            stringBuilder.append("(");
            tree2str(root.right);
            stringBuilder.append(")");
        }
        return stringBuilder.toString();
    }
}

13. 二叉树前序非递归遍历实现

在线OJ:144. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1

img

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

示例 2

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

示例 3

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

示例 4

img

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

示例 5

img

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

提示

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

进阶:递归算法很简单,你可以通过迭代算法完成吗?

💯解题思路:

  1. 使用一个栈储存前序序遍历的结点。
  2. 按照前序遍历的搜索顺序, 获取并保存非空结点的数据, 并将结点入栈。
  3. 遍历到结点为空时,此时将当前遍历的结点更新为栈顶结点的右结点,并将栈顶元素出栈。
  4. 重复上述步骤,直到栈和结点都为空。
class Solution {
    //方法三:利用栈非递归实现
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                list.add(cur.val);
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            cur = top.right;
        }
        return list;
    }
    //方法一
    /*List<Integer> list = new ArrayList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        if(root == null) {
            return list;
        } 
        list.add(root.val);
        preorderTraversal(root.left);
        preorderTraversal(root.right);
        return list;

    }*/
    
    //方法二
    /*public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null) {
            return list;
        } 
        list.add(root.val);
        List<Integer> leftTree = preorderTraversal(root.left);
        list.addAll(leftTree);
        List<Integer> rightTree = preorderTraversal(root.right);
        list.addAll(rightTree);
        
        return list;
    }*/
}

14. 二叉树中序非递归遍历实现

在线OJ:94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

示例 1

img

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

示例 2

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

示例 3

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

提示

树中节点数目在范围 [0, 100] 内

-100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

💯解题思路:

  1. 使用一个栈储存中序遍历的结点。
  2. 按照搜索顺中序遍历的搜索顺序,将非空结点入栈。
  3. 遍历到结点为空时,此时将当前遍历的结点更新为栈顶结点的右结点,获取并保存栈顶结点的数据, 将栈顶元素出栈。
  4. 重复上述步骤,直到栈和结点都为空。
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>(); // 1
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);  // 2
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            list.add(top.val);  // 3
            cur = top.right;
        }
        return list;
    }
}

15. 二叉树后序非递归遍历实现

在线OJ:145. 二叉树的后序遍历

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

示例 1

img

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

示例 2

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

示例 3

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

提示

树中节点的数目在范围 [0, 100] 内

-100 <= Node.val <= 100

进阶:递归算法很简单,你可以通过迭代算法完成吗?

💯解题思路:

我们应该知道前, 中, 后序遍历在遍历时的搜索顺序其实是相同的, 区别在于访问结点数据的时机不同, 再对比上面的前,中序两道题, 上面的两道题在遍右树时, 左树和根中的数据已经访问过了, 所以在更新当前遍历结点为栈顶元素右结点时, 栈顶元素可以直接出栈; 但在后序遍历中更新为右结点时, 根节点中的元素还没有被访问, 此时是不能出栈的; 要在访问了根结点数据之后再出栈;

同样的, 按照上面两题的遍历思路, 只是更改访问结点数据时机可不可行呢, 按照此时的思路代码如下:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>(); 
        TreeNode cur = root;
        TreeNode flag = null;  
        while(cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if(top.right == null) {
                list.add(top.val);
                stack.pop();
            }else{
                cur = top.right;
            }
        }
        return list;
    }
}

其实上面的代码是有一些问题的, 看这几行代码,

img

这里的代码, 当结点的右子树不为空的时候, 我们再去遍历右子树, 这里就有问题了, 当遍历完一个结点的右子树后返回, 由于结点没有出栈, top又重新指向了原来的结点, 此时再去遍历top的右树就重复遍历了, 造成了死循环,

为了解决这个问题,我们可以用一个标记来提前记录遍历完后的右子树的根结点, 记为flag; 有了flag, 当我们遍历完右子树, top结点又回到这棵右子树的根结点, ,如果此时该结点的右子树为空或者flag与该结点右子树的根结点相同,就表示右子树已经遍历完成了,不需要再次遍历了, 就将这个结点出栈并保存结点中的数据, 并将出栈结点用flag记录(表示这棵树已经遍历完了),

所以重新整理思路如下:

  1. 使用一个栈储存后序遍历的结点,定义一个标记引用flag,用来记录上一次遍历完的右子树。
  2. 获取并保存非空结点数据,并将结点入栈。
  3. 遍历到结点为空时,获取栈顶元素,判断栈顶的元素的右子树是否为空和判断该元素的右子树是否与上一次遍历完的右子树flag相等, 如果右子树为空或者该结点的右子树与flag相等, 那么该树已经遍历完成,此时获取并保存根结点数据, 栈顶元素栈并赋值给flag,否则更新当前结点为该结点的右结点去遍历右子树。
  4. 重复上述步骤,直到栈和结点都为空。
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>(); // 1
        TreeNode cur = root;
        TreeNode flag = null; // 1
        while(cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);  // 2
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if(top.right == null || top.right == flag) {
                list.add(top.val);  // 3
                flag = stack.pop();
            }else{
                cur = top.right;
            }
        }
        return list;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVAweb第一次总结作业

1.什么是html HTML的全称为超文本标记语言(Hyper Text Markup Language)&#xff0c;是一种标记语言。它包括一系列标签&#xff0c;通过这些标签可以将网络上的文档格式统一&#xff0c;使分散的Internet资源连接为一个逻辑整体。 HTML文本是由HTML命令组成的描述性文本&…

【Python】import模块的多种操作

前言 记录一下关于Python在导入模块时候一些操作~ 知识点&#x1f4d6;&#x1f4d6; Python魔法方法&#xff1a;__all__ Python内置模块&#xff1a;importlib 实现 指定导出的变量 当你在使用 from xxx import * 时候&#xff0c;可以通过 __all__ 来指定可被导出的变…

每天五分钟机器学习:通过学习曲线判断模型是过拟合还是欠拟合

本文重点 本节课程我们学习使用学习曲线来判断某一个学习算法是否处于偏差、方差问题。学习曲线其实就是训练误差和验证误差关于样本m的曲线,我们将通过学习曲线来判断该算法是处于高偏差问题,还是处于高方差问题。 高偏差问题(欠拟合) 注意:这个m表示训练集数据样本…

王二涛研究组揭示丛枝菌根共生与根瘤共生的协同进化机制

2021年&#xff0c;中国科学院分子植物科学卓越创新中心王二涛团队在《Molecular Plant》发表了“Mycorrhizal Symbiosis Modulates the Rhizosphere Microbiota to Promote Rhizobia Legume Symbiosis”研究论文&#xff0c;该研究通过定量微生物组、微生物共发生网络及微生物…

mac for m1(arm):安装redis的四种方式(本机安装、homebrew安装、虚拟机安装、docker安装)

0. 引言 redis作为当今最常用的非关系型数据库&#xff0c;被广泛应用于数据缓存场景。而mac m1采用arm芯片&#xff0c;使得众多软件安装成为问题&#xff0c;今天我们来看mac m1如何安装redis 1.本机安装redis 1、下载redis安装包&#xff1a;https://redis.io/download/ …

subplots()--matplotlib

1. 函数功能 成一个画布和若干子区。2. 函数语法 matplotlib.pyplot.subplots(nrows1, ncols1, *, sharexFalse, shareyFalse, squeezeTrue, subplot_kwNone, gridspec_kwNone, **fig_kw)3. 函数参数与示例 参数含义nrows, ncols画布被分成的行、列数squeeze布尔值&#xf…

【畅购商城】用户登录

用户登录 构建页面&#xff1a;Login.vue步骤一&#xff1a;创建Login.vue步骤二&#xff1a;绘制通用模块<template> <div> <TopNav></TopNav> <div style"clear:both;"></div> <HeaderLogo></HeaderLogo> <div…

嵌入式开发--CubeMX使用入门教程

嵌入式开发–CubeMX使用入门教程 CubeMX简介 传统的单片机开发时&#xff0c;需要针对片上外设做各种初始化的工作&#xff0c;相当麻烦。 CubeMX是ST公司出品的一款图形化代码生成工具&#xff0c;通过图形化界面&#xff0c;可以非常直观的配置好各种片上外设&#xff0c;时…

一个方便IO单元测试的C#扩展库

对于我们.Net程序员&#xff0c;System.Web.Abstractions我们都非常熟悉&#xff0c;主要作用于Web可以实现单元测试&#xff0c;他是在.Net framework 3.5 sp1开始引入的,很好的解决项目表示层不好做单元测试的问题&#xff0c;这个库所有类都是Wrapper/Decorator模式的。今天…

[SpringBoot] Spring Boot注册Web原生组件/拦截器HandlerInterceptor

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

风控建模坏样本太少,不要再用过采样和欠采样了,试下这种更有效的方法

样本数据不平衡是我们建模场景中经常遇到的问题&#xff0c;由于目标类别的分布占比差异较大&#xff0c;使得模型训练难以取得较好的拟合效果&#xff0c;甚至模型结果在实际应用中无效。举个最常见的例子&#xff0c;在信贷场景中构建反欺诈模型时&#xff0c;训练样本数据的…

(附源码)计算机毕业设计SSM垃圾分类综合服务系统

&#xff08;附源码&#xff09;计算机毕业设计SSM垃圾分类综合服务系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

(27)语义分割--cityscape数据集的读取和使用

1、主要参考 (1) 图像分割cityscape数据集使用介绍 - 知乎 (2)torchvision支持很多现成的数据集 Datasets — Torchvision 0.13 documentation 。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。。。 2、下载…

CTFHub | 整数型注入

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

【Vue 快速入门系列】列表的基本使用

文章目录前言列表的基本使用Key的原理列表过滤列表排序前言 本篇文章讲述Vue中最基本的列表使用&#xff0c;如何迭代列表取值&#xff0c;如何对列表进行过滤、排序等。 列表的基本使用 在Vue中使用列表的时候灰常简单&#xff0c;只需要将Vue属性内的列表数据与dom标签进行…

[架构之路-57]:目标系统 - 平台软件 - 用户空间驱动与硬件抽象层HAL

目录 前言&#xff1a; 第1章 驱动程序功能设计 1.1 关于用户空间驱动 1.2 硬件驱动程序的四大功能概述 1.3 OAM管理面功能&#xff1a;站在管理源的角度&#xff0c;看如何监控使能和监控硬件。 1.4 控制面功能&#xff1a;站在业务的角度看&#xff0c;如何使能和监控硬…

基于Android的五子棋游戏APP设计

目 录 第一章&#xff1a;绪论 1 1.1智能手机与Android系统的发展历程 1 1.1.1 智能手机 1 1.1.2 Android系统基本情况介绍 2 1.2课题现状及应用前景 3 1.2.1 五子棋简介 3 1.2.2 课题现状及应用前景 3 第二章&#xff1a;开发环境的搭建 5 2.1 系统开发环境 5 2.2 系统开发环境…

SpringCould(一)

视频链接&#xff1a;https://www.bilibili.com/video/BV1f94y1U7AB/?vd_source9545770e4a2968c05878ffac8589ec6c 视频选集&#xff1a;P1— P15 文章目录1.微服务简介1.1 单体架构不足1.2 微服务1.3 微服务的特点1.4 微服务的自动部署&#xff08;CI/CD&#xff09;(持续集成…

一文了解数据结构

目录 数据结构 什么是数据结构 链表 数组 栈 队列 哈希表 堆 数据结构 什么是数据结构 「数据结构」决定了数据的顺序和位置关系.数据存储于内存时&#xff0c;决定了数据顺序和位置关系的便是「数据结构」 链表 「链表」中的数据呈线性排列。链表中添加删除数据比较…

多旋翼无人机仿真 rotors_simulator:基于PID控制器的位置控制---水平位置控制

多旋翼无人机仿真 rotors_simulator&#xff1a;基于PID控制器的位置控制---水平位置控制前言水平位置控制串级P控制收敛结果收敛过程串级PID控制收敛结果收敛过程结果总结前言 无人机&#xff08;Unmanned Aerial Vehicle&#xff09;&#xff0c;指的是一种由动力驱动的、无…