一、前言
接着上条关于二叉树的进阶的面试题 , 这篇博客也继续用二叉树这种数据结构来解决复杂的编程问题
二、二叉树的最近公共祖先
2.1 题目
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
2.2 实现原理
① 解题 : 公共祖先就是两个节点的共同的祖先 , 自己也可以是自己的祖先 , 最近公共祖先 , 就是公共祖先中离目标节点最近的祖先 , 靠递归 , 从根节点出发 , 同时找给定的两个节点 , 查找=>比较根节点+左子树查找+右子树查找
② 查找的情况:
1.从该节点出发,两个节点都没找到(该节点不是公共祖先)
2.从该节点出发,只找到其中一个节点(该节点只是一个节点的祖先,不是公共祖先)
3.从该节点出发,如果两个节点都找到了就是公共祖先
4.如果找到的两个目标节点分散在三个位置的两处,此时该节点就是最近公共祖先(三个位置 左子树 右子树 根节点) , 如果同时出现在该节点的左子树/右子树,此时该节点一定不是最近公共祖先 , 针对每个子树都去查找pq
2.3 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//借助这个成员变量保存最近公共祖先
private TreeNode lca=null;
public TreeNode lowestCommonAncestor(TreeNode root,TreeNode p,TreeNode q){
if(root==null){
return null;
}
findNode(root,p,q);
return lca;
}
//如果找到p/q返回true
private boolean findNode(TreeNode root,TreeNode p,TreeNode q){
if(root==null){
return false;
}
//后序遍历
int left=findNode(root.left,p,q)?1:0;
int right=findNode(root.right,p,q)?1:0;
//访问根节点
int mid=(root==p||root==q)?1:0;
if(left+right+mid==2){
lca=root;
}
return (left+right+mid)>0;
}
}
三、将二叉搜索树转换成一个排序的双向链表
3.1 实现原理
① 二叉搜索树:左子树中所有节点都小于根节点 右子树中所有节点都大于根节点(不能存在两个值相同的节点)
② 查找过程类似于二分查找
③ 二叉搜索树的中序遍历结果是有序的
④ 树的Node 包含left和right 转成双向链表 left指向前一个节点(prev) right指向后一个(next)
⑤ 中序遍历(递归) 访问节点操作就是构建链表 , 把当前节点连接到链表中
3.2 代码实现
class TreeNode{
int val=0;
TreeNode left=null;
TreeNode right=null;
public TreeNode(int val) {
this.val = val;
}
}
public class Main {
//返回值的含义是链表的头结点
public TreeNode Convert(TreeNode pRootOfTree){
if(pRootOfTree==null){
return null;
}
if(pRootOfTree.left==null&&pRootOfTree.right==null){
//只有一个根节点,得到的链表也只有一个节点
return pRootOfTree;
}
//递归处理左子树,把左子树转换成双向链表
//left就是左子树链表的头结点
TreeNode left=Convert(pRootOfTree.left);
//处理根节点,把根节点追加到左子树链表的末尾
//这就相当于链表尾插,找到前面链表的最后一个节点
TreeNode leftTail=left;
while(leftTail!=null&&leftTail.right!=null){
leftTail=leftTail.right;
}
//上述循环结束之后leftTail就是left这个链表的最后一个节点
//把当前节点尾插过去
//left为空 leftTail就是空,防止空指针异常
if(leftTail!=null) {
leftTail.right = pRootOfTree;
pRootOfTree.left = leftTail;
}
//递归处理右子树,right就是右子树链表的头结点
TreeNode right=Convert(pRootOfTree.right);
if(right!=null){
//根节点和右子树连接到一起
right.left=pRootOfTree;
pRootOfTree.right=right;
}
//返回整个链表头结点
return left!=null?left:pRootOfTree;
}
}
四、从前序与中序遍历序列构造二叉树
4.1 题目
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
4.2 实现原理
① 根据前序遍历和中序遍历构建二叉树 , 先序遍历结果第一个元素一定是根节点 ,
② 先序遍历结果=>根节点+左子树先序结果+右子树先序结果
③ 中序结果=>左子树中序结果+根节点+右子树中序结果
④ preorder这个数组的长度和inorder相同
⑤ 辅助遍历 能够搞清楚preorder访问到那个元素 , 还是通过index记录
4.3 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private int index=0;
public TreeNode buildTree(int[] preorder,int[] inorder){
index=0;
//借助下面这个方法进行递归 为了更好的描述子树,在方法中加入一对参数
//表示当前子树对应的中序遍历结果的区间
return buildTreeHelper(preorder,inorder,0,inorder.length);
}
//[inorderLeft,inorderRight)表示当前这个子树的中序遍历区间
//递归过程中 preorder和inorder参数始终没变 inorderLeft和inorderRight在发生变化
//inorderLeft inorderRight是判断元素有效
private TreeNode buildTreeHelper(int[] preoder,int[] inorder,int inoderLeft,int inoderRight){
if(inoderLeft>=inoderRight){
//中序区间为空
return null;
}
if(index>=preoder.length){
//先序中的所有元素都访问完
return null;
}
//根据index取出当前树的根节点的值,并构建根节点
TreeNode newNode=new TreeNode(preoder[index]);
//知道根节点 根据根节点去中序中找到左子树和右子树范围
//左子树对应中序区间是[inorderLeft,pos)
//右子树对应中序区间是[pos+1,inorderRight)
int pos=find(inorder,inoderLeft,inoderRight,newNode.val);
index++;//index只自增一次
newNode.left=buildTreeHelper(preoder,inorder,inoderLeft,pos);
newNode.right=buildTreeHelper(preoder,inorder,pos+1,inoderRight);
return newNode;
}
private int find(int[] inorder, int inoderLeft, int inoderRight, int val) {
for(int i=inoderLeft;i<inoderRight;i++){
if(inorder[i]==val){
return i;
}
}
return -1;
}
}
Tips :
根据后序和中序构建一颗二叉树 , 原理和代码与该题类似 , 逆置后序遍历的结果 , 相当于是一个镜像的先序 , 即"NRL"
五、根据二叉树创建字符串
5.1 题目
你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。空节点则用一对空括号 “()” 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
示例 1:
输入: 二叉树: [1,2,3,4]
1
/ \
2 3
/
4
输出: “1(2(4))(3)”
解释: 原本将是“1(2(4)())(3())”,
在你省略所有不必要的空括号对之后,
它将是“1(2(4))(3)”。
示例 2:
输入: 二叉树: [1,2,3,null,4]
1
/ \
2 3
\
4
输出: “1(2()(4))(3)”
解释: 和第一个示例相似,
除了我们不能省略第一个对括号来中断输入和输出之间的一对一映射关系。
5.2 实现原理
括号省略的规则:
① 左右子树都为空 左右子树空括号可以省略
② 左子树为空 右子树非空 不可以省略
③ 左子树非空 右子树为空可以省略
5.3 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//StringBuilder线程不安全 不涉及多线程就可以去使用
//StringBuffer线程安全
private StringBuilder sb=new StringBuilder();
public String tree2str(TreeNode t){
if(t==null){
return "";
}
helper(t);
sb.deleteCharAt(0);
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
private void helper(TreeNode root) {
if(root==null){
return;
}
//先访问当前节点,此时的访问操作就是把元素加到StringBuilder中
sb.append("(");
sb.append(root.val);
helper(root.left);
if(root.left==null&&root.right!=null){
sb.append("()");
}
helper(root.right);
sb.append(")");
}
}
这是第二部分关于二叉树进阶面试题的总结,将在下条博客继续持续更新这一数据结构的相关题型~