【LC】二叉树应用强化OJ

在这里插入图片描述

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

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;
    }
    */
}

其他实现:

将需要的两个信息(左右树是否为平衡二叉树, 左右树的高度), 整合到了一个对象中, 但核心不变, 还是判断上面的三种情况.

class Solution {
    // 判断该头节点所在二叉树是不是平衡二叉树, 首先要判断该树的左树和右树分别是不是平衡二叉树
    public static class Info {
        // 标记是不是平衡树
        public boolean isBalanced;
        // 整棵树的高度
        public int height;

        public Info(boolean isBalanced, int height) {
            this.isBalanced = isBalanced;
            this.height = height;
        }
    }
    public boolean isBalanced(TreeNode root) {
        return process(root).isBalanced;
    }
    public static Info process(TreeNode root) {
        if (root == null) {
            return new Info(true, 0);
        }
        Info leftInfo = process(root.left);
        Info rightInfo = process(root.right);
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        boolean isBalanced = leftInfo.isBalanced && rightInfo.isBalanced
                && Math.abs(leftInfo.height - rightInfo.height) < 2;
        return new Info(isBalanced, height);
    }
}

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;
    }
}

其他写法:

核心思路和上面是一样的.

class Solution105 {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || inorder == null || preorder.length != inorder.length) {
            return null;
        }
        HashMap<Integer, Integer> valueIndexMap = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            valueIndexMap.put(inorder[i], i);
        }
        return childBuildTree(preorder, 0, preorder.length - 1, inorder,
                0, inorder.length - 1, valueIndexMap);
    }

    /**
     * @param pre 前序遍历的区间
     * @param l1 要构建子树在前序数组的左范围
     * @param r1 要构建子树在前序数组的右范围
     * @param in 中序遍历的区间
     * @param l2 要构建子树在中序数组的左范围
     * @param r2 要构建子树在中序数组的右范围
     * @return
     */
    public static TreeNode childBuildTree(int[] pre, int l1, int r1, int[] in, int l2, int r2,
                                          HashMap<Integer, Integer> valueIndexMap) {
        // 没有子树了, 比如该数只有左单只 1->2->3 这样的数
        if (l1 > r1) {
            return null;
        }
        // l1 位置的元素就是要构建数的头节点
        TreeNode head = new TreeNode(pre[l1]);
        // 只有一个节点
        if (l1 == r1) {
            return head;
        }
        // 在中序遍历的数组中找到头节点的位置(可以提前建立一个表, 查表就行了, 使用 HashMap即可)

        /*int find = l2;
        while (in[find] != pre[l1]) {
            find++;
        }*/

        int find = valueIndexMap.get(pre[l1]);
        // 中序遍历找到的头节点左边就是左子树的元素, 右边就是右子树的元素
        // 再结合前序遍历进行构造
        head.left = childBuildTree(pre, l1 + 1, l1 + find - l2, in, l2, find - 1, valueIndexMap);
        head.right = childBuildTree(pre, l1 + find - l2 + 1, r1, in, find + 1, r2, valueIndexMap);
        return head;
    }
}

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;
    }
}

17. 二叉树的自底向上的分层遍历

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

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

示例 1:

img

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

示例 2:

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

示例 3:

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

提示:

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

💯解题思路:

这个题是在上面层序遍历那道题的基础之上做出了进一步要求, 整体思路不变, 还是利用一个队列来得到数的每一层, 只不过如果还是采用顺序表存放每一层的话, 最后需要将顺序表给逆序一下;

或者可以使用链表的头插来存在每一层, 就省略了最后逆序的过程.

class Solution {
    // 使用链表, 可以直接进行头插
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> ans = new LinkedList<>();
        if (root == null) {
            return ans;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                list.add(cur.val);
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            ans.add(0, list);
        }
        return ans;
    }

    // 使用顺序表, 结果需要逆序一下
    /*public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                list.add(cur.val);
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            ans.add(list);
        }
        // Collections.reverse(ans);
        int left = 0;
        int right = ans.size() - 1;
        while (left < right) {
            List<Integer> val = ans.get(left);
            ans.set(left, ans.get(right));
            ans.set(right, val);
            left++;
            right--;
        }
        return ans;
    }*/
}

18. 验证二叉搜索树

在线OJ:98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

img

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

示例 2:

img

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4

提示:

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

💯解题思路:

如何判断是否为二叉搜索树? 即: 中序遍历严格递增.

对于一棵树的根节点 root, 有下述三种情况:

  1. 如果当前节点左树右树都不为空, 且左右树都是搜索二叉树, 且当前节点值比左树最大值都大, 比右树最小值要小, 则以 root 为根节点的树是二叉搜索树.
  2. 如果左树为空, 且右树是搜索二叉树, 且当前节点值比右树最小值要小.
  3. 如果右树为空, 且左树是搜索二叉树, 且当前节点值比左树最大值要大.
  4. 如果左右树都是空, 默认当前节点就是二叉搜索树.

除此之外, 以 root 为节点的二叉树都不是搜索二叉树.

根据上述可能性, 我们可以确认当前节点需要左右树给自己汇报如下三个信息.

  1. 左右树的最大值

  2. 左右树的最小值

  3. 左右树是否是搜索二叉树

有以上三个信息, 就可以判断上述的四种可能性了.

class Solution {
    // 判断头节点所在树是不是一棵二叉搜索树
    // 如果该树的左子树的最大值小于头节点的值并且右子树的最小值大于头节点人的值
    // 那么这棵树就是二叉搜索树
    public static class Info {
        public boolean isBST;
        public int max;
        public int min;

        public Info(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max = max;
            this.min = min;
        }
    }

    public boolean isValidBST(TreeNode root) {
        return process(root).isBST;
    }

    public static Info process(TreeNode root) {
        if (root == null) {
            return null;
        }
        Info leftInfo = process(root.left);
        Info rightInfo = process(root.right);
        int max = root.val;
        int min = root.val;
        if (leftInfo != null) {
            max = Math.max(leftInfo.max, max);
            min = Math.min(leftInfo.min, min);
        }
        if (rightInfo != null) {
            max = Math.max(rightInfo.max, max);
            min = Math.min(rightInfo.min, min);
        }

        boolean leftIsBST = leftInfo == null ? true : leftInfo.isBST;
        boolean rightIsBST = rightInfo == null ? true : rightInfo.isBST;
        boolean leftMaxLessRoot = leftInfo == null ? true : (leftInfo.max < root.val);
        boolean rightMinMoreRoot = rightInfo == null ? true : (rightInfo.min > root.val);
        
        boolean isBST = leftIsBST && rightIsBST && leftMaxLessRoot && rightMinMoreRoot;

        return new Info(isBST, max, min);
    }
}
评论 86
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韵秋梧桐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值