二叉树的学习——进阶面试题篇

目录

前言

一、二叉树的构建和遍历

二、二叉树的完全性检验

三、二叉树最大宽度

四、从前序和中序遍历序列构建二叉树

五、从中序和后序遍历序列构造二叉树

六、二叉树的最近公共祖先

七、二叉搜索树与双向链表


前言

最近一直在学习数据结构,学习的过程中不禁感叹编程真是一件有趣的事情,学完二叉树之后,彻底拜倒在递归的石榴裙下,脑子里只剩下一句话——多么神奇的递归啊!尤其是在写各种面试题时,这里就记录一下和二叉树有关的面试题吧!


一、二叉树的构建和遍历

OJ链接:KY11 二叉树遍历

题目描述如下:

编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

解题思路:

  • 根据二叉树的先序遍历构建二叉树,先从头遍历字符串,如果第一个字符是'#',那么说明此二叉树是个空树,反之,先创建根节点,然后递归处理左子树,最后递归处理右子树。

代码如下:

import java.util.Scanner;
/**
 * 二叉树的构建和遍历
 */
public class NowCoder_KY11 {
    public static class TreeNode {
        char val;
        TreeNode left;
        TreeNode right;
        public TreeNode(char val) {
            this.val = val;
        }
        public TreeNode(char val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }
    //表示前序遍历处理到哪个字符了
    private static int index=0;
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        //多组输入
        while(sc.hasNext()){
            //每次读取一个字符串
            String str=sc.next();
            //根据前序遍历构建二叉树
            TreeNode root=preOrderBuild(str);
            //中序遍历结果
            inOrder(root);
            System.out.println();
            index=0;
        }
    }
    /**
     * 根据前序遍历来构建二叉树
     * @param str
     * @return
     */
    private static TreeNode preOrderBuild(String str) {
        char ch=str.charAt(index);
        if (ch=='#'){
            return null;
        }
        TreeNode root=new TreeNode(ch);
        index++;
        //递归处理左子树
        root.left=preOrderBuild(str);
        index++;
        //递归处理右子树
        root.right=preOrderBuild(str);
        return root;
    }
    /**
     * 中序遍历二叉树:左根右
     * @param root
     */
    private static void inOrder(TreeNode root) {
        if (root==null){
            return;
        }
        inOrder(root.left);
        System.out.println(root.val+" ");
        inOrder(root.right);
    }
}

二、二叉树的完全性检验

OJ链接:判断一个二叉树是否是完全二叉树

题目描述如下:

给定一个二叉树的 root ,确定它是否是一个完全二叉树 。

解题思路:

引入一个标记位来区分二叉树的两种状态:

状态一:此时二叉树的每个节点都有左右子树。

切换状态的条件:

  1. 当碰到第一个只有左子树没有右子树的节点
  2. 碰到第一个叶子节点

状态二:此时二叉树全都是叶子节点。

代码如下:

/**
 * 判断一颗二叉树是否是完全二叉树
 */
public class Num958_IsCompleteTree {
    public boolean isCompleteTree(TreeNode root) {
        Queue<TreeNode> queue=new LinkedList<>();
        queue.offer(root);
        //当前所处状态
        boolean isSecondState=false;
        while(!queue.isEmpty()){
            TreeNode cur=queue.poll();
            //第一状态,所有节点全部都有左右子树
            if (!isSecondState) {
                if (cur.left != null && cur.right != null) {
                    queue.offer(cur.left);
                    queue.offer(cur.right);
                    //当遇到第一个有左子树没有右子树的节点时,转换状态
                } else if (cur.left != null) {
                    isSecondState = true;
                    //并将左子树入栈
                    queue.offer(cur.left);
                    //当遇到一个有右子树没有左子树的节点时,直接false
                } else if (cur.right != null) {
                    return false;
                    //当遇到第一个叶子节点时
                } else {
                    isSecondState = true;
                }
            }else{
                //第二状态,位于第二状态下的节点全部是叶子节点
                if (cur.left!=null||cur.right!=null) {
                    return false;
                }
            }
        }
        return true;
    }
}

三、二叉树最大宽度

OJ链接:二叉树最大宽度

题目描述如下:

给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。

解题思路:

  • 根节点从1开始编号,父节点编号为k时,左孩子的编号为2k,右孩子的编号为2k+1,层序遍历时存储每个节点和它的编号,当每一层遍历结束,取出最左侧的节点编号L和最右侧的节点编号R,该层的宽度就是R-L+1,比较每层宽度,选出最大的。

代码如下:

/**
 * 求一颗二叉树的最大宽度
 */
public class Num662_WidthOfBinaryTree {
    public int widthOfBinaryTree(TreeNode root) {
        if (root==null) {
            return 0;
        }
        //定义最大宽度
        int maxWide=0;
        Queue<NodeWidthNum> queue=new LinkedList<>();
        //将根节点和它的编号入队
        queue.offer(new NodeWidthNum(root,1));
        while(!queue.isEmpty()){
            //定义每一层的宽度
            int levelWide=0;
            //取得此时队列中的元素个数
            int size=queue.size();
            //最左边的子树的编号
            int L=0;
            //最右边的子树的编号
            int R=0;
            for (int i = 0; i < size; i++) {
                NodeWidthNum cur=queue.poll();
                //最左侧节点
                if (i==0){
                    L=cur.num;
                }
                //最右侧节点
                if (i == size - 1){
                    R=cur.num;
                }
                if (cur.node.left!=null){
                    queue.offer(new NodeWidthNum(cur.node.left,2*cur.num));
                }
                if (cur.node.right!=null){
                    queue.offer(new NodeWidthNum(cur.node.right,2*cur.num+1));
                }
            }
            //此时每层宽度就时R-L+1
            levelWide=R-L+1;
            //二叉树的最大宽度
            maxWide=Math.max(levelWide,maxWide);
        }
        return maxWide;
    }
    //将TreeNode包装,增加一个节点的编号
    private class NodeWidthNum{
        //每个二叉树的节点
        TreeNode node;
        //该节点编号
        int num;
        public NodeWidthNum(TreeNode node, int num) {
            this.node = node;
            this.num = num;
        }
    }
}

四、从前序和中序遍历序列构建二叉树

OJ链接:从前序和中序遍历序列构建二叉树

题目描述如下:

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

解题思路:

  • 前序遍历:根左右,当前结果的第一个值一定是当前树的根节点
  • 中序遍历:左根右,左子树都在根的左侧,右子树都在根的右侧
  • 不断在前序遍历结果集中取出元素,此元素作为当前树的根节点,拿着根节点去中序遍历结果集中查找该节点值所在位置,数组左侧是左子树,数组右侧是右子树,递归上述流程,直到前序遍历结果集全部遍历完

代码如下:

/**
 * 根据前序遍历和中序遍历序列构建二叉树
 */
public class Num105_BuildTreeByPreAndIn {
    //当前处理到哪个节点了
    private int index=0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeHelper(preorder,inorder,0,preorder.length-1);
    }
    /**
     * 在preorder的[left,right]中借助中序遍历来还原二叉树
     * @param preorder
     * @param inorder
     * @param left
     * @param right
     * @return
     */
    private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int left, int right) {
        if (left>right){
            return null;
        }
        //当index等于前序遍历的长度时
        if (index==preorder.length){
            return null;
        }
        //创建根节点
        TreeNode root=new TreeNode(preorder[index]);
        index++;
        //在中序遍历中找到当前节点的位置,返回索引值
        int pos=find(root.val,inorder);
        //递归寻找左子树
        root.left=buildTreeHelper(preorder,inorder,left,pos-1);
        //递归寻找右子树
        root.right=buildTreeHelper(preorder, inorder,pos+1, right);
        return root;
    }
    /**
     * 在中序遍历中找到val,并返回它的索引值
     * @param val
     * @param inorder
     * @return
     */
    private int find(int val, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i]==val){
                return i;
            }
        }
        return -1;
    }
}

五、从中序和后序遍历序列构造二叉树

OJ链接:从中序和后序遍历序列构造二叉树

题目描述如下:

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗二叉树。

解题思路:

  • 中序遍历:左根右,左子树在根节点的左侧,右子树在根节点的右侧
  • 后序遍历:左右根,根节点就是该序列的最后一个元素
  • 只需要将后序遍历结果集转置,就变成了根右左,先取根节点,然后递归处理右子树,再递归处理左子树

代码如下:

/**
 * 根据中序遍历和后序遍历构建二叉树
 */
public class Num106_BuildTreeByInAndPost {
    private int index=0;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int[] postorderReverse=Reverse(postorder);
        return buildTreeHelper(inorder,postorderReverse,0,postorder.length-1);
    }
    //将后序遍历序列翻转
    private int[] Reverse(int[] postorder) {
        int l=0;
        int r=postorder.length-1;
        while(l<r){
            int ret=postorder[l];
            postorder[l]=postorder[r];
            postorder[r]=ret;
            l++;
            r--;
        }
        return postorder;
    }
    /**
     * 在postorder的[left,right]中借助中序遍历来还原二叉树
     * @param inorder
     * @param postorder
     * @param left
     * @param right
     * @return
     */
    private TreeNode buildTreeHelper(int[] inorder, int[] postorder, int left, int right) {
        if (left>right){
            return null;
        }
        if (index==postorder.length){
            return null;
        }
        TreeNode root=new TreeNode(postorder[index]);
        index++;
        int pos=find(root.val,inorder);
        //先递归访问右子树
        root.right=buildTreeHelper(inorder, postorder,pos+1,right);
        //再递归访问左子树
        root.left=buildTreeHelper(inorder, postorder,left,pos-1);
        return root;
    }
    //在中序遍历中找到val,并返回它的索引
    private int find(int val, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i]==val){
                return i;
            }
        }
        return -1;
    }
}

六、二叉树的最近公共祖先

OJ链接:二叉树的最近公共祖先

题目描述如下:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

解题思路:

节点p和q的祖先,就是从该节点开始遍历,能同时将p和q这两个找到就是它们的祖先。而最近公共祖先x,从x出发,无论使用哪种遍历方式都可以把节点p和q都找到,且p和q一定不在同一颗子树,则x是p和q的最近公共祖先。p和q可能出现的位置:①左子树和右子树 ② 根节点和左子树  ③根节点和右子树。

代码如下:

/**
 * 二叉树的最近公共祖先
 */
public class Num236_LowestCommonAncestor {
    private TreeNode lca;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q){
        findNode(root, p, q);
        return lca;
    }
    private boolean findNode(TreeNode root, TreeNode p, TreeNode q) {
        if (root==null){
            return false;
        }
        //用整型变量来记录是否找到节点
        //先在左子树中寻找,如果找到p或q、或者p和q同时找到,结果就为1,没找到就为0
        int left=findNode(root.left, p, q)?1:0;
        //然后在右子树寻找,如果找到p或q、或者p和q同时找到,结果就为1,没找到就为0
        int right=findNode(root.right, p, q)?1:0;
        //然后判断根节点
        int x=(root==p||root==q)?1:0;
        //如果三者之和等于2,就说明p和q绝对不在同一颗子树,此时的节点就为p和q的最近公共祖先
        if (left+right+x==2){
            lca=root;
        }
        return (left+right+x)>0;
    }
}

七、二叉搜索树与双向链表

OJ链接:二叉树搜索树转换成排序双向链表

题目描述如下:

注意:

  1. 要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
  2. 返回链表中的第一个节点的指针
  3. 函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
  4. 你不用输出双向链表,程序会根据你的返回值自动打印输出

解题思路:

  • 要得到一个排序的双向链表,就要将BST进行中序遍历
  • 原地操作,left左子树就相当于前驱节点,right右子树就相当于后继节点

代码如下:

/**
 *输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
 */
public class NowCoder_JZ36 {
    public static TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree==null){
            return null;
        }
        //对树进行中序遍历,先将左子树转为双向链表
        //此时leftHead就是左链表的头节点
        TreeNode leftHead=Convert(pRootOfTree.left);
        //左链表的尾节点leftTail
        TreeNode leftTail=leftHead;
        while(leftTail!=null&&leftTail.right!=null){
            leftTail=leftTail.right;
        }
        //进行拼接
        if (leftTail!=null){
            pRootOfTree.left=leftTail;
            leftTail.right=pRootOfTree;
        }
        //再递归处理右子树,将右子树转换为链表
        TreeNode rightHead=Convert(pRootOfTree.right);
        //和根节点进行拼接
        if (rightHead!=null){
            pRootOfTree.right=rightHead;
            rightHead.left=pRootOfTree;
        }
        //如果左子树为空,则返回根节点,如果左子树不为空,返回左链表头节点
        return leftHead==null?pRootOfTree:leftHead;
    }
}

创作不易,你是否有收获呢?如果有收获的话就留下你的👍吧!!! 

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

怎样让大排不硬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值