首页 > 编程语言 >Java二叉树经典例题

Java二叉树经典例题

时间:2024-07-17 21:56:40浏览次数:21  
标签:例题 Java cur return 二叉树 TreeNode null root 节点

目录

一.相同的树

二.翻转二叉树

三.平衡二叉树

四.对称二叉树

五.根据前(后)和中序排序构建二叉树

1.前序和中序构建

2.后序和中序构建

六.二叉树的层序遍历

七.二叉树非递归遍历

1.前序遍历

2.中序遍历

3.后序遍历

八.总结


前言:

前面说过树是通过递归定义的。做二叉树的题,递归是避不开的。做递归题的思路就是终止条件+“公式”(我的个人理解,可能不是很严谨),只要准确找到终止条件和“公式”即可。

一.相同的树

根据两个根节点判断两棵树是否相同,对应题目:LeetCode:100. 相同的树

思路:先看根节点是不是一样的,如果一样,就看结构是否一样(二叉树左右是有顺序的),即左子树是否一样,右子树是否一样(递归)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    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);
    }
}

终止条件就是上面的三个if。三个if,三种情况可以停下的条件。isSameTree(p.left,q.left) && isSameTree(p.right,q.right) 就是我所说的“公式”。

拓展:LeetCode:572. 另一棵树的子树

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

思路:有了上个题的铺垫这个题就轻易很多。我们只需要一个一个节点的遍历,到一个节点就判断它和subRoot是不是同一颗树即可。

当然,我们还要得出终止条件。节点如果到叶节点的左右节点了,即root==null时,可以返回了。因为此时root怎么可能与subRoot对上(原题说个subRoot不为空),return false即可。

下面就要罗列“公式”了,首先判断root是不是和subRoot是一棵树,如果不是,再看root的左子树是不是与subRoot一样,再看root的右子树是不是与subRoot一样。如果都不一样,那么就是没有子树与subRoot一样,返回false。

整理一下:

1.判断root是不是和subRoot是一棵树

2.root的左子树是不是与subRoot一样

3.root的右子树是不是与subRoot一样

这样是不是清晰一些,能直接感受到“公式”的罗列。

public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root==null){
            return false;
        }

        if(isSameTree(root,subRoot)){
            return true;
        }

        if(isSubtree(root.left,subRoot)){
            return true;
        }

        if(isSubtree(root.right,subRoot)){
            return true;
        }

        return false;
    }

二.翻转二叉树

对应题目:LeetCode:226. 翻转二叉树

这个题的思路很清晰:

1.交换root 的左右子树

2.交换root 左子树的左右子树

3.交换root 右子树的左右子树

终止条件就是“到底了”,root==null 了。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root==null){
            return null;
        }

        TreeNode tem=root.left;
        root.left=root.right;
        root.right=tem;

        invertTree(root.left);
        invertTree(root.right);

        return root;
    }
}

可以看到,方法内容就是上面罗列的条件和“公式”。先终止条件,再1、2、3,最后return。

三.平衡二叉树

对应题目:LeetCode:110. 平衡二叉树

何为平衡二叉树?该树所有节点的左右子树的深度相差不超过1。

思路:先判断当前节点的左右子树是否深度相差不超过1,如果不超过,紧接着判断当前节点的左子树的左右子树的深度相差不超过1,当前结点的右子树的左右子树的深度相差不超过1。

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null){
            return true;
        }

        if(root.left==null && root.right==null){
            return true;
        }

        if(Math.abs(getHeight(root.left)-getHeight(root.right))<2){
            return isBalanced(root.left)&& isBalanced(root.right);
        }else{
            return false;
        }
    }

    public int getHeight(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);


        return leftHeight >rightHeight
        ? leftHeight+1 : rightHeight+1;
    }

}

四.对称二叉树

对应题目:LeetCode:101. 对称二叉树

root的左节点要和右节点对称;左节点要和右节点对称,要左节点的左节点与右节点的右节点对称,左节点的右节点和右节点的左节点对称。

步骤:

1.先判断是否为空

2.如果不为空,判断是否一方为空,一方不为空

3.节点值是否一样,如果一样,再看左右子树

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null){
            return true;
        }

        return isSymmetricChild(root.left,root.right);
    }

    public boolean isSymmetricChild(TreeNode rl,TreeNode rr){
        if(rl==null && rr==null){
            return true;
        }

        if(rl==null && rr!=null || rl!=null && rr==null){
            return false;
        }

        if(rl.val==rr.val){
            return isSymmetricChild(rl.left,rr.right) && isSymmetricChild(rl.right,rr.left);
        }else{
            return false;
        }
    }
}

五.根据前(后)和中序排序构建二叉树

这种问题在选择题遇到的比较多,但是也可以通过代码实现。

1.前序和中序构建

对应题目:LeetCode:105. 从前序与中序遍历序列构造二叉树

首先先弄明白前序和中序数组的目的。前序可以确定根节点,中序可以根据根节点来判断左右子树部分,前序继续判断左右子树的根节点,中序继续分左右树。

用一个 i 来遍历前序数组,写一个 find 方法目的是为了在中序数组中找到当前root的位置,找到root后,root的左面那一部分数组就是root的左子树的内容,右面的同理。在根据前序数组的遍历,完成构建。

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int inEnd = inorder.length-1;

        return buildTreeChild(preorder,0,inorder,inEnd);
    }

    public int size=0;    //用于存储前序数组遍历到的位置

    public TreeNode buildTreeChild(int[] preorder, int inBegin,int[] inorder,int inEnd){
        if(size<preorder.length){
            //如果size没有超出范围

            //先new新节点
            TreeNode cur=new TreeNode(preorder[size]);
            size++;

            //新节点的左孩子
            if(inBegin>find(inorder,cur.val)-1){
                cur.left=null;
            }else{
                cur.left=buildTreeChild(preorder,inBegin,inorder,find(inorder,cur.val)-1);
            }

            //新节点的右孩子
            if(find(inorder,cur.val)+1>inEnd){
                cur.right=null;
            }else{
                cur.right=buildTreeChild(preorder,find(inorder,cur.val)+1,inorder,inEnd);
            }

            return cur;
        }else{
            return null;
        }
    }

    public int find(int[] inorder,int x){
        for(int i=0;i<inorder.length;i++){
            if(inorder[i] == x){
                return i;
            }
        }
        return 0;
    }
}

2.后序和中序构建

对应题目:LeetCode:106. 从中序与后序遍历序列构造二叉树

思路与前序和中序构建相同,只不过是倒着遍历后序的数组(因为根在后面),先确定右子树,再确定左子树(因为倒着遍历先遍历到右节点)。

class Solution {

    public int size;    //这里的size要主动获取
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int inEnd=inorder.length-1;
        size=postorder.length-1;
        return  buildTreeChild(inorder,0,postorder,inEnd);
    }
    
    public TreeNode buildTreeChild(int[] inorder, int inBegin,int[] postorder,int inEnd){
        if(size>=0){
            TreeNode cur=new TreeNode(postorder[size]);
            size--;    //注意是--

            //确定右子树
            if(find(inorder,cur.val)+1>inEnd){
                cur.right=null;
            }else{
                cur.right=buildTreeChild(inorder,find(inorder,cur.val)+1,postorder,inEnd);
            }

            //确定左子树
            if(inBegin>find(inorder,cur.val)-1){
                cur.left=null;
            }else{
                cur.left=buildTreeChild(inorder,inBegin,postorder,find(inorder,cur.val)-1);
            }
            return cur;
        }else{
            return null;
        }

    }

    public int find(int[] inorder,int x){
        for(int i=0;i<inorder.length;i++){
            if(inorder[i] == x){
                return i;
            }
        }
        return 0;
    }
}

六.二叉树的层序遍历

对应题目:LeetCode:102. 二叉树的层序遍历

层序遍历用到了队列(Queue)。首先,获取队列里元素个数;然后,依次取出,并放入链表中;最后将取出的元素的左右节点(不为空)放入队列完成循环。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list=new LinkedList<>();

        if(root==null){
            return list;
        }

        Queue<TreeNode> queue=new LinkedList<>();

        queue.offer(root);

        while(!queue.isEmpty()){
            int size=queue.size();    //获取元素数量
            List<Integer> tem=new LinkedList<>();
            while(size!=0){
                size--;
                TreeNode cur=queue.poll();    //取出元素
                tem.add(cur.val);    //加进链表
                //将左节点放入队列
                if(cur.left!=null){
                    queue.offer(cur.left);
                }
                //将右节点放入队列
                if(cur.right!=null){
                    queue.offer(cur.right);
                }
            }
            list.add(tem);
        }

        return list;
    }
}

七.二叉树非递归遍历

前中后序非递归遍历都用到了栈(stack)。根据不同的需求入栈出栈以达到效果。

1.前序遍历

首先,root入栈,如果root有左节点,左节点继续入栈,不断循环左节点入栈,并且不断将左节点值加入链表中,直到找不到左节点位置。找不到左节点,往回走,看栈顶元素有没有右节点,如果有,右节点入栈。

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>();

        if(root==null){
            return list;
        }

        Stack<TreeNode> stack=new Stack<>();

        TreeNode cur = root;

        while(cur!=null || !stack.empty()){
            while(cur!=null){
                stack.push(cur);
                list.add(cur.val);
                cur=cur.left;
            }

            if(!stack.empty()){
                TreeNode top=stack.pop();
                cur=top.right;
            }
        }

        return list;
    }
}

2.中序遍历

代码没什么太大变化,仅是 list.add(top.val); 这行换了个位置。这里是一直将左节点放入栈中的但是不放入链表中,直到某一节点没有左节点结束。这时从栈顶取出元素放入链表。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new LinkedList<>();

        Stack<TreeNode> stack=new Stack<>();

        TreeNode cur=root;

        while(cur!=null || !stack.empty()){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }

            if(!stack.empty()){
                TreeNode top=stack.pop();
                list.add(top.val);
                cur=top.right;
            }
        
        }

        return list;
    }
}

3.后序遍历

后序又与前两个不同,用前两个的思路,行不通,无法阻止根放入链表。

这时要再定义一个节点记录右边被没被放入链表。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list=new LinkedList<>();
        
        if(root == null){
            return list;
        }

        Stack<TreeNode> stack = new Stack<>();

        TreeNode cur = root;
        TreeNode prev = null;

        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }

            TreeNode top = stack.peek();

            if (top.right == null || top.right == prev) {
                list.add(top.val);
                stack.pop();
                prev = top;
            } else {
                cur = top.right;
            }
        }

        return list;
    }
}

八.总结

通过上述的题目可以看到,递归思想在做这类题时很重要。如果实在是看不明白就手动模拟看一下。其实做了一定量的递归类型的题目后根本就不用模拟了,直接条件+公式就可以做出来。用来递归的那个方法,本身就是用来求取某种情况的。如:翻转的方法,这个方法本身就是反转某一个树用的。这里可以这样看,题目让我们翻转的树,其实就是某一个大树的子树。这样是不是更容易理解,我们写的方法,可以理解为递归过程中子树的一个方法。在方法中用本身的方法去求其子树的方法,这不又圆回来了。

总之,实在不理解就多练,多练就会了。

标签:例题,Java,cur,return,二叉树,TreeNode,null,root,节点
From: https://blog.csdn.net/lllsure/article/details/140502493

相关文章

  • 树与二叉树
    树与二叉树目录树与二叉树基本概念基本术语根双亲结点孩子节点节点的层次节点的度叶子树的高度有序树与无序树二叉树二叉树概念:二叉树基本特性满二叉树/完美二叉树:完全二叉树:平衡二叉树:退化二叉树:二叉树的链式存储树的遍历BST树基本概念插入节点删除节点遍历代码基本概念树是一......
  • JavaSE--分支、循环结构
    流程控制语句    流程控制语句就是在Java中用来设置Java代码如何运行及运行顺序的。    分类:    顺序结构【默认结构】    分支结构【选择结构】    循环结构【重复结构】顺序结构    顺序结构是Java程序中用来设置Java......
  • JavaSE--基础语法
    JDK、JVM、JRE的区别以及作用    JDK:Java开发工具包(包括JRE和相关工具包)    JVM:Java虚拟机(通过JVM可以实现跨平台开发)    JRE:Java运行环境(包含JVM及Java核心类库)Java语言特性    简单    面向对象    跨平台(一次编译......
  • Java生成二维码的方法,QRCode、JQuery、Zxing
    QRcode国标简单示例相关资源下载zxing实现生成 /***生成二维码**@paramwidth*@paramheight*@paramname*@paramformat*@paramcontent*/publicstaticStringgenerateQRCodeByZxing(intwidth,i......
  • Java面试 : String
    串池:StringTable,可以理解为一个对象数组["a","b","ab"]每一个元素都是一个字符串对象1.常量池与串池的关系Strings1="a";Strings2="b";Strings3="ab";上述代码的运行过程:常量池中的信息会被加载到运行时常量池中,这时abab都是常量池中的符号,还没有变成Java......
  • Java核心API——Object类
    Object简介         Object类是所有类的根类,这意味着在Java中创建的每一个类都直接或间接地继承自Object类(除了Object类本身以外,因为它没有父类)    看到这里你或许还是不明为什么要有Object类下面我就详细解释。首先这里就不得不提到Java这门语言让人熟......
  • Java——IO流
    1.IO流简介流是一个抽象的概念,它是一串连续动态的数据集合Java.io包中几乎包含了所有操作输入和输出需要的类,同时也支持很多格式,比如:基本类型,对象,本地化字符等io包中主要包括四大抽象类,分别是Writer,OutputStream,InputStream,Reader,其中Writer和OutputStream属于......
  • 常用的 JavaScript 数组处理方法
    1.map方法用于创建一个新数组,其结果是该数组中的每个元素调用一个提供的函数后返回的结果。letitems=[{id:1,name:'item1'},{id:2,name:'item2'},{id:3,name:'item3'}];letitemNames=items.map(item=>item.name);console.log(itemNames);......
  • Java语言基础-03
    1.Scanner接收用户输入的数据:packageday04;importjava.util.Scanner;//1.导入一个扫描仪//Scanner的演示publicclassScannerDemo{publicstaticvoidmain(String[]args){//创建类CommandBySwitch,接收用户输入的命令command(int),并输出......
  • 【java计算机毕设】网上购书管理系统MySQL servlet JSP项目设计源代码 期末寒暑假作业
    目录1项目功能2项目介绍3项目地址1项目功能【java计算机毕设】网上购书管理系统MySQLservletJSP项目设计源代码期末寒暑假作业小组作业 2项目介绍系统功能:servlet网上购书管理系统包括管理员、用户两种角色。管理员功能包括订单管理(已处理,未处理),顾客管理(添......