目录
前言
最近一直在学习数据结构,学习的过程中不禁感叹编程真是一件有趣的事情,学完二叉树之后,彻底拜倒在递归的石榴裙下,脑子里只剩下一句话——多么神奇的递归啊!尤其是在写各种面试题时,这里就记录一下和二叉树有关的面试题吧!
一、二叉树的构建和遍历
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
,确定它是否是一个完全二叉树 。
解题思路:
引入一个标记位来区分二叉树的两种状态:
状态一:此时二叉树的每个节点都有左右子树。
切换状态的条件:
- 当碰到第一个只有左子树没有右子树的节点
- 碰到第一个叶子节点
状态二:此时二叉树全都是叶子节点。
代码如下:
/**
* 判断一颗二叉树是否是完全二叉树
*/
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链接:二叉树搜索树转换成排序双向链表
题目描述如下:
注意:
- 要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
- 返回链表中的第一个节点的指针
- 函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
- 你不用输出双向链表,程序会根据你的返回值自动打印输出
解题思路:
- 要得到一个排序的双向链表,就要将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;
}
}
创作不易,你是否有收获呢?如果有收获的话就留下你的👍吧!!!