二叉树练习习题题集二(Java)

思路:从上到下,从左到右,先遍历到的先打印,可以用队列的特性"先进先出"实现,先放进根结点,并创建一个一维数组,然后求此时队列的个数size,每次弹出队列的一个元素放进一维数组,size--,如果size==0,就将这个一维数组放进存一维数组的一维数组,然后将不为null的左右孩子放进队列,如此循环即可

/**
 * 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 List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();//存放一维数组的一维数组
        Queue<TreeNode> queue=new LinkedList<TreeNode>();//创建一个队列
        if(root==null){//如果根结点为空
            return list;
        }
        queue.offer(root);//将根节点放进队列
        while(!queue.isEmpty()){
            List<Integer> list1=new ArrayList<Integer>();//创建一维数组
            int size=queue.size();//求此时队列的个数(即这一层的结点有多少个)
            while(size!=0){
                TreeNode node=queue.poll();//弹出队列的一个元素
                size--;//个数减一
                list1.add(node.val);//放进一维数组
                if(size==0){//如果此时个数等于0,说明这一层的结点都弹出完了
                    list.add(list1);//将一维数组放进存放一维数组的一维数组
                }
                if(node.left!=null){//将不为null的左孩子放进队列
                    queue.offer(node.left);
                }
                if(node.right!=null){//将不为null的右孩子放进队列
                    queue.offer(node.right);
                }
            } 
        }
        return list;
    }
}

思路:只是变成了从下到上,只需要在从上到下的层序遍历的基础上,每次将一维数组放进存放一维数组的一维数组由原来的尾插变为头插即可(即只需要改一行代码,其他一模一样)

/**
 * 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 List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();//存放一维数组的一维数组
        Queue<TreeNode> queue=new LinkedList<TreeNode>();//创建一个队列
        if(root==null){//如果根结点为空
            return list;
        }
        queue.offer(root);//将根节点放进队列
        while(!queue.isEmpty()){
            List<Integer> list1=new ArrayList<Integer>();//创建一维数组
            int size=queue.size();//求此时队列的个数(即这一层的结点有多少个)
            while(size!=0){
                TreeNode node=queue.poll();//弹出队列的一个元素
                size--;//个数减一
                list1.add(node.val);//放进一维数组
                if(size==0){//如果此时个数等于0,说明这一层的结点都弹出完了
                    list.add(0,list1);//将一维数组放进存放一维数组的一维数组(头插)
                }
                if(node.left!=null){//将不为null的左孩子放进队列
                    queue.offer(node.left);
                }
                if(node.right!=null){//将不为null的右孩子放进队列
                    queue.offer(node.right);
                }
            }

        }
        return list;
    }
}

法一:

思路:判断一个结点是否是p,q的公共祖先,无非有一下三种情况:

1.p在左,q在右

2.p,q都在左边

3.p,q都在右边

此时只有第一种情况才能判断该结点是公共祖先,而第二,三种不能判断,还需要继续递归下去,让该结点的左孩子作为新的公共祖先候选人,然后让该结点的右孩子作为新的公共祖先候选人,如果左孩子这棵树找到了p或者q,那么左孩子这个结点就是公共祖先,同理,右孩子这棵树找到了p或者q,那么右孩子这个结点就是公共祖先。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null){//为空返回null
            return null;
        }
        if(p==root||q==root){//如果是要找的结点,返回该结点
            return root;
        }
        TreeNode leftTree=lowestCommonAncestor(root.left,p,q);//往左树找
        TreeNode rightTree=lowestCommonAncestor(root.right,p,q);//往右树找
        if(leftTree!=null&&rightTree!=null){//如果左右树都各有一个结点
            return root;//说明该结点是公共祖先
        }else if(leftTree!=null){//如果左树有,但右树为空,说明左树的根结点为公共祖先
            return leftTree;
        }else{//如果右树有,但左树为空,说明右树的根结点为公共祖先
            return rightTree;
        }
    }
}

法二:

在树里这个叫公共祖先,但是跟之前写过的交叉链表其实几乎一样,求两条链表的交叉点,方法就是求出两条链表的长度,然后求得差值,让长的先走差值步,然后以相同的速度一起走,相同的时候就是交叉点。这里树也同理,求得p,q两个结点的路径和长度,让路径较长的先走差值步,然后以相同的速度一起走,相同的时候就是公共祖先。那么怎么求根节点到p和q的路径呢

创建一个栈,用栈来记录路径,因为当我们找结点的时候,有可能这个结点不在这个路径上,所以这个结点要弹出,如果是队列的话,就不能将这个最后的结点弹出,而要先弹出根节点,所以要用栈来实现,发现这个结点不在路径上,因为"后进先出",刚好就能把这个结点弹出去。

然后还是用递归去找左右孩子,如果左右孩子都没找到,就说明这个结点不在路径上,如果找到了,说明这个结点在路径上

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    //root是遍历到的结点,node是要找的结点,stack是存放路径的地方
    public boolean getPath(TreeNode root,TreeNode node,Stack<TreeNode> stack){
        if(root==null){//如果为空,说明没有找到
            return false;
        }
        stack.push(root);//不为空,将这个结点先进栈
        if(root==node){//如果这个结点是要找到结点
            return true;//说明找到了
        }
        boolean ret=getPath(root.left,node,stack);//找左孩子
        if(ret){//如果左孩子是要找到结点
            return true;//说明找到了
        }
        ret=getPath(root.right,node,stack);//找右孩子
        if(ret){//如果右孩子是要找到结点
            return true;//说明找到了
        }
        stack.pop();//左右孩子都没有,说明这个结点不在我们要找的结点的路径上
        return false;//没有找到
    }
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //特殊情况
        if(root==null){
            return null;
        }
        if(root==p||root==q){
            return root;
        }
        //存放两个结点的路径
        Stack<TreeNode> stackP=new Stack<>();
        Stack<TreeNode> stackQ=new Stack<>();
        getPath(root,p,stackP);
        getPath(root,q,stackQ);
        //判断哪个路径更长,长的先走差值步
        int sizeP=stackP.size();
        int sizeQ=stackQ.size();
        if(sizeP>sizeQ){
            int size=sizeP-sizeQ;
            while(size!=0){
                stackP.pop();
                size--;
            }
        }
        if(sizeP<sizeQ){
            int size=sizeQ-sizeP;
            while(size!=0){
                stackQ.pop();
                size--;
            }
        }
        //走完差值步,以相同速度走,相同则是公共祖先
        while(!stackP.isEmpty()&&!stackQ.isEmpty()){
            if(stackP.peek()==stackQ.peek()){
                return stackP.pop();
            }else{
                stackP.pop();
                stackQ.pop();
            }
        }
        //没有公共祖先(即不在同一棵树)
        return null;
    }
}

思路:

首先要了解怎么通过前序和中序创建二叉树的步骤

前序是用来找根的,中序是用来判断左右树的结点的

第一步:从下标0开始遍历前序数组

第二步:每从前序数组遍历一个元素,就去中序数组找对应的元素并记录该元素在中序数组的下标

第三步:那么该中序下标左边的元素都在它的左树,右边的元素都在它的右树

第四步:循环第一步到第三步,找到刚刚那个结点的左右孩子的结点,直到该下标左右没有元素,说明该结点没有左右孩子,是叶子结点

所以现在就是将如上步骤转换成代码即可

class Solution {
    //用来遍历前序数组的下标(必须用全局变量,如果是局部变量,递归回来局部变量又没有发生变化)
    public int preindex=0;
    //用来在中序数组中找到前序数组遍历到的元素
    //在arr数组中(中序数组),从begin到end区间里,找val,返回下标i,没有就返回-1
    private int findval(int[] arr,int begin,int end,int val){
        for(int i=begin;i<=end;i++){
            if(arr[i]==val){
                return i;
            }
        }
        return -1;
    }
    //用来创建二叉树,preorder:前序数组,rt:根结点的下标,inorder:中序数组
    //inbegin:这棵树的孩子结点在中序数组的起点下标,inend:这棵树的孩子结点在中序数组的结束下标
    public TreeNode buildTreeChild(int[] preorder,int rt,int[] inorder,int inbegin,int inend){
        //如果没有孩子结点,返回null
        if(inbegin>inend){
            return null;
        }
        //创建根结点
        TreeNode root=new TreeNode(preorder[rt]);
        //往后遍历前序数组的下标++
        preindex++;
        //找到该根结点在中序数组的下标
        int rootindex=findval(inorder,inbegin,inend,preorder[rt]);
        //创建左孩子这棵树的根
        root.left=buildTreeChild(preorder,preindex,inorder,inbegin,rootindex-1);
        //创建右孩子这颗树的根
        root.right=buildTreeChild(preorder,preindex,inorder,rootindex+1,inend);
        //返回根节点
        return root;
    }
    //输入前序数组和中序数组创建二叉树
    public TreeNode buildTree(int[] preorder, int[] inorder){
        return buildTreeChild(preorder,preindex,inorder,0,inorder.length-1);
    }
}

思路:

同上面一样,只不过由前序变成后序

因为前序是:根,左,右;后序是:左,右,根

所以后序数组中,最后一个元素才是根结点,如何往前遍历,先是右树的根,再是左树的根

所以只需要将上一题的代码稍作修改即可

class Solution {
    //用来遍历后序数组的下标(必须用全局变量,如果是局部变量,递归回来局部变量又没有发生变化)
    public int postindex=0;
    //用来在中序数组中找到后序数组遍历到的元素
    //在arr数组中(中序数组),从begin到end区间里,找val,返回下标i,没有就返回-1
    private int findval(int[] arr,int begin,int end,int val){
        for(int i=begin;i<=end;i++){
            if(arr[i]==val){
                return i;
            }
        }
        return -1;
    }
    //用来创建二叉树,inorder:中序数组,rt:根结点的下标,postorder:后序数组
    //inbegin:这棵树的孩子结点在中序数组的起点下标,inend:这棵树的孩子结点在中序数组的结束下标
    public TreeNode buildTreeChild(int[] inorder,int rt,int[] postorder,int inbegin,int inend){
        //如果没有孩子结点,返回null
        if(inbegin>inend){
            return null;
        }
        //创建根结点
        TreeNode root=new TreeNode(postorder[rt]);
        //往前遍历后序数组的下标--
        postindex--;
        //找到该根结点在中序数组的下标
        int rootindex=findval(inorder,inbegin,inend,postorder[rt]);
        //创建右孩子这颗树的根
        root.right=buildTreeChild(inorder,postindex,postorder,rootindex+1,inend);
        //创建左孩子这棵树的根
        root.left=buildTreeChild(inorder,postindex,postorder,inbegin,rootindex-1);
        //返回根节点
        return root;
    }
    //输入中序数组和后序数组创建二叉树
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        postindex=postorder.length-1;
        return buildTreeChild(inorder,postindex,postorder,0,postindex);
    }
}

思路:

先理解题目意思,就是根结点直接拼接到字符串,然后按照前序遍历的顺序,有孩子就给()包着,孩子里面套孩子,如果有左孩子但没右孩子可以省略右孩子的(),如果没有左孩子但有右孩子,要用()标记左孩子,代表左孩子为null,如果没左右孩子,就拼接),然后返回

如上图,步骤为:

1.按照前序遍历顺序,根结点直接拼接到字符串,现在字符串为"1"

2."1"这个结点有左孩子,拼接"(",然后递归到"2"这个结点,现在字符串为"1("

3.到"2"这个结点,将结点的值拼接,拼接"2","2"这个结点有左孩子,拼接"(",然后递归到"4"这个结点,现在字符串为"1(2("

4.到"4"这个结点,将结点的值拼接,拼接"4","4"这个结点没有左右孩子,用")"包住4返回,现在字符串为"1(2(4)"

5.回到"2"这个结点,2没有右孩子,"()"可以省略,用")"包住2返回,现在字符串为"1(2(4))"

6.回到"1"这个结点,1有右孩子,然后递归到"3"这个结点,现在字符串为"1(2(4))("

7.到"3"这个结点,将结点的值拼接,拼接"3","3"这个结点没有左右孩子,用")"包住3返回,现在字符串为"1(2(4))(3)"

8.根结点"1"的左右孩子遍历完了,返回字符串即可

注:本题底下有提示,不为空树

class Solution {
    //因为用String的不可变性,+=效率太低了所以可以用StringBuffer或者StringBuilder
    StringBuffer str=new StringBuffer();
    public String tree2str(TreeNode root) {
        //添加值
        str.append(root.val);
        //如果有左孩子
        if(root.left!=null){
            str.append("(");
            tree2str(root.left);
            str.append(")");
            //有左无右
            if(root.right==null){
                //无事发生
                str.append("");
            }else{//有左有右
                str.append("(");
                tree2str(root.right);
                str.append(")");
            }
        }else{//如果没有左孩子
            if(root.right==null){//无左无右
                //无事发生
                str.append("");
            }else{//无左有右
                str.append("()");//记录左孩子为null
                str.append("(");
                tree2str(root.right);
                str.append(")");
            }
        }
        return str.toString();//转换为String类型返回
    }
}
相关推荐
Daniel 大东21 分钟前
BugJson因为json格式问题OOM怎么办
java·安全
不去幼儿园23 分钟前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh25 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海1 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
幽兰的天空1 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou5 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
冰帝海岸5 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象6 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试