二叉树--经典面试题1

目录

1.检查两棵树是否相同

2.另一棵树的子树

3.判断一棵二叉树是否是平衡二叉树

4.对称二叉树

5.二叉树的构建及遍历

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

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


1.检查两棵树是否相同

  • 题目描述:

  •  代码

    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q != null) return false;
        if(p != null && q == null) return false;
        //代码走到这里,说明两者都不为空,或者都为空
        if(p == null) return true;
        if(p.val != q.val) return false;;
        //左右子树结构和值都相等才算是一棵相同的树
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
  • 思路

1.两个二叉树要是相同的树,那么它们的结构和值就必须都相同;

2.结构和值如何比较:先比较根,看是否都有,如果一个为空,另一个不为空,则结构不同,如果两个都为空,则结构和值都相同,如果都不为空,则比较根的值;然后递归判断分别以左右孩子作为根的两棵树是否相同,重复该操作,直到比较完每一个结点,,,,


2.另一棵树的子树

  • 题目描述

  •  代码

    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q != null) return false;
        if(p != null && q == null) return false;
        if(p == null) return true;
        if(p.val != q.val) return false;;
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null) return false;
        //先判断根节点root这棵树是否包含subRoot这棵树,再分别以root的左子树和右子树作为根,来进行判断
         if(isSameTree(root, subRoot)) return true;
         if(isSubtree(root.left, subRoot)) return true;
         if(isSubtree(root.right, subRoot)) return true;
         return false;
    }
  • 思路

       做这道题,我们需要在上一道题的基础上来完成,判断一棵二叉树是否是另一棵树的子树,也就是判断这棵树是否包含另一棵树,从函数的参数上来看,传过去的是两棵树的根,由此可知,我们先判断的是,两棵树是否相同,再分别以这棵树的左子树和右子树为根传过去进行判断,也就是判断这棵树的左子树或者右子树是否包含(>=)另一棵树,一直这样递归下去,直到在大树里面找到另一棵树的子树。

  • 图解 


 3.判断一棵二叉树是否是平衡二叉树

  • 题目描述

  •  代码一

时间复杂度为O(n^2),待会写一个时间复杂度为O(n)的代码;

    //求高度的代码 
    public int height(TreeNode root) {
        if(root == null) {
            return 0;
        }
        if(root.left == null && root.right == null) {
            return 1;
        }
        int leftH = height(root.left);
        int leftR = height(root.right);
        return leftH > leftR ? leftH+1 : leftR+1;
    }
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        int leftH = height(root.left);//左树的高度
        int rightH = height(root.right);//右树的高度
        //求出左右子树的高度后,判断差值是否 <= 1 ,满足就继续往下递归,看看左右子树是否也是平衡树
        return (Math.abs(leftH - rightH) <= 1) && (isBalanced(root.left) && isBalanced(root.right));
    }
  • 思路

这个题需要借助求高度的代码,求平衡二叉树其实思路很简单,只要计算根结点的这棵树的左子树的高度和右子树的高度,然后相减判断是否 <= 1 ,如果满足,则继续往下判断,以根的左右孩子作为根,继续重复操作。当然这不是最好的方法,时间复杂度太高了。

  • 图解 


  •  代码二 

时间复杂度为O(n),大大提高了效率;

    public int height(TreeNode root) {
        if(root == null) return 0;
        int leftH = height(root.left);
        int rightH = height(root.right);
        //我在求高度的时候就检查下面的树是否平衡
        if(leftH >= 0 && rightH >= 0 && Math.abs(leftH - rightH) <= 1) {
            return Math.max(leftH,rightH) + 1;
        } else {
            return -1;
        }
    }
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        return height(root) >= 0;
    }
  • 思路

如果按照第一种写法,在算最初的根节点那棵树时,就已经分别计算下面的左子树,或者右子树的左右子树的高度,如果按照那种方式继续递归下去,将会重复计算大量数据,效率非常慢;于是第二种方法,就是改造求高度的函数,我们在递归的过程中就直接判断左右子树高度的差值是否 <= 1,满足我就返回左右子树高度中较大的一个加一,不满足就返回-1,因为高度不可能是负数,这样将省略很多不必要的计算;还要注意的一点就是,判断条件还要满足求出的左右子树高度均大于0,否则负数也将在递归的过程中参与计算了。

  • 对比图 


4.对称二叉树

  • 题目描述

  •  代码

    private boolean isSymmetricChild(TreeNode leftTree, TreeNode rightTree) {
        if(leftTree != null && rightTree == null) return false;
        if(leftTree == null && rightTree != null) return false;
        if(leftTree == null) return true;
        if(leftTree.val != rightTree.val) return false;
        //左子树的左,右子树的右,左子树的右,右子树的左
        return isSymmetricChild(leftTree.left,rightTree.right) && isSymmetricChild(leftTree.right, rightTree.left);
    }
    public boolean isSymmetric(TreeNode root) {
        return isSymmetricChild(root.left, root.right);
    }
  • 思路

这道题与相同的树有着异曲同工之妙,唯一不同的地方,就是递归不同,这里也是先比较结构,再比较值的相同与否,在比较完根的结构和值后,我们需要递归比较的是左子树的左,和右子树的右,左子树的右和右子树的左,其他的操作都对应着。


 5.二叉树的构建及遍历

  • 题目描述

  •  代码

import java.util.*;

class TreeNode {
    char val;
    TreeNode left;
    TreeNode right;
    public TreeNode(char val) {
        this.val = val;
    }
}

public class Main {
    //主要代码
    public static int i = 0;
    //字符串是先序遍历的,所以创建这棵树就也是按照先序遍历来创建
    public static TreeNode createTree(String str) {
        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 static void inorder(TreeNode root) {
        if(root == null) return;
        inorder(root.left);
        System.out.print(root.val + " ");
        inorder(root.right);
    }
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while(scan.hasNextLine()) {
            String str = scan.nextLine();
            TreeNode root = createTree(str);
            inorder(root);
        }
    }
}
  • 思路

1.这道题的关键代码就是创建二叉树这个函数,题目给的是一串先序遍历的字符串,所以我们创建二叉树的时候,也是用先序遍历来创建的,这里可能会有人问:只知道先序遍历也能创建一棵二叉树吗?平时,我们必须要知道中序遍历和前后序遍历的其中一种,就能创建二叉树,但是这里不一样,它给的字符串,虽然我们只知道要按前序遍历来创建,但是它规定了空树在哪,所以只有一种情况,就可以创建二叉树。

创建好的二叉树:

2.得到这棵树之后,我们就可以从字符串入手去创建这棵树了,我们需要拿到字符串的每一个字符,换做以前,我们肯定要弄一个循环,但是二叉树的真正创建方式是用递归来创建的,所以不能用循环,我们需要一个变量 i ,通过 charAt()方法不断获取字符串的每一个字符,如果 i 放在函数里面的话,每次递归,i 都会重新变为 0 ,所以我们需要将 i 定义在方法的外面,接下来,就是实例化一个根节点,然后不断以左右子树作为根节点,递归下去。

  • 图解


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

  • 题目描述

  •  代码一

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) {
            return null;
        }
        //其中一个为root,则返回root
        if(root == p || root == q) {
            return root;
        }

        //找左右子树,是从下往上找的
        TreeNode leftRet = lowestCommonAncestor(root.left, p, q);
        TreeNode rightRet = lowestCommonAncestor(root.right, p, q);

        //如果都不为空,那么说明p,q在两侧
        if(leftRet != null && rightRet != null) {
            return root;
        } else if(leftRet != null) {
            //如果左不为空,那么左为空,说明p,q都在左侧,且leftRet为root
            return leftRet;
        } else if(rightRet != null){
            //如果右不为空,那么左为空,说明p,q都在右侧,且rightRet为root
            return rightRet;
        }
        return null;
    }
  • 思路一

如果这棵树是一棵二叉搜索树,那怎么找公共祖先?

只讨论 p,无非就三种情况:

 得出结论:

1. p 和 q 一个在一侧,此时的最近公共祖先是root;

2. p 或 q 其中一个为 root ,此时的最近公共祖先是 p 或者 q;

3. p 和 q 都在左侧,或者都在右侧,此时需要分别递归左侧或者右侧继续查找。 


  • 代码二

    private boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
        if(root == null || node == null) {
            return false;
        }
        stack.push(root);
        //递归过程中,如果根等于 p 或 q , 说明路径找完了,依次返回
        if(root == node) {
            return true;
        }

        //往左递归找 p 或 q
        boolean leftRet = getPath(root.left, node, stack);
        if(leftRet == true) {
            return true;
        }

        //往右递归找 p 或 q
        boolean rightRet = getPath(root.right, node, stack);
        if(rightRet == true) {
            return true;
        }
        //因为getPath方法,一进来就入栈,如果没有返回true,说明入栈的就不是路劲上的结点,直接弹出,然后返回false
        stack.pop();
        return false;
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //1.分别获取 p,q 的路径,依次添加到栈中
        Stack<TreeNode> stack1 = new Stack<>();
        getPath(root, p, stack1);

        Stack<TreeNode> stack2 = new Stack<>();
        getPath(root, q, stack2);

        //2.求出两个栈的大小,让大的先走差值步
        int size1 = stack1.size();
        int size2 = stack2.size();

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

        //再一起走,直到栈顶元素相等
        while(!stack1.empty() && ! stack2.empty()) {
            if(stack1.peek() == stack2.peek()) {
                return stack1.pop();
            } else {
                stack1.pop();
                stack2.pop();
            }
        }
        return null;
    }
  • 思路二

如果二叉树的表示方式,是孩子双亲表示法。那么此时,这个最近公共祖先,可以被优化为,求链的交点;

解法:

1.使用两个栈,存储从根节点到指定结点的路径上的所有结点;

2.比较两个栈的大小,让 size 大的栈出差值个结点;

3.此时同时开始出栈,如果栈顶元素相同,那么这个值就是最近公共祖先

  • 图解


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

  • 题目描述

  •  代码

    //调整结构--中序遍历

    //prev必须放在方法外面,不然每次递归,都变成null了
    TreeNode prev = null;
    public void ConvertChild(TreeNode pCur) {
        //不用担心最后一个结点有无后继,当prev指向最后一个结点的时候,pCur已经为null了
        if(pCur == null) return;
        ConvertChild(pCur.left);
        //绑前驱
        pCur.left = prev;
        //空结点只能作为头节点的前驱,没有后继
        if(prev != null) {
            prev.right = pCur;
        }
        //绑后继
        prev = pCur;
        ConvertChild(pCur.right);
    }
        
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return null;
        
        ConvertChild(pRootOfTree);
        
        TreeNode head = pRootOfTree;
        while(head.left != null) {
            head = head.left;
        }
        return head;
    }
  • 思路

做这道题之前只需要把握两个点:

  • 图解

谢谢观看!!!

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Master_hl

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

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

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

打赏作者

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

抵扣说明:

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

余额充值