Java二叉树经典例题

目录

一.相同的树

二.翻转二叉树

三.平衡二叉树

四.对称二叉树

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

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

八.总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值