二叉树进阶题目记录
实现二叉树的非递归 先序, 中序, 后序遍历
非递归实现
二叉树的构建和遍历
题目描述:
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
import java.util.Scanner;
public class Main {
public static class TreeNode{
char val;
TreeNode leftNode;
TreeNode rightNode;
public TreeNode (char val){
this.val = val;
}
}
static int index = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while(in.hasNext()){
index = 0;
String s = in.nextLine();
char[] array = s.toCharArray();
TreeNode root = build(array);
inorder(root);
System.out.println();
}
}
//中序遍历
private static void inorder(TreeNode root) {
if(root == null){
return;
}
inorder(root.leftNode);
System.out.print(root.val + " ");
inorder(root.rightNode);
}
//先序构建
private static TreeNode build(char[] array) {
//叶子节点返回空值
if(array[index] == '#'){
return null;
}
// ABC##DE#G##F###
//中序 c b e g d f a
TreeNode root = new TreeNode(array[index]);
index++;
root.leftNode = build(array);
index++;
root.rightNode = build(array);
return root;
}
}
题目分析:
题目给定先序遍历字符串ABC##DE#G##F### 的字符串,要求使用这个字符串进行勾线一个二叉树.
先序遍历是以中左右的顺序遍历的,我们可以知道只有先序遍历结果是不能得出正确的二叉树结构的.但是题目给出了空节点的位置.
代码分析:
TreeNode root = new TreeNode(array[index]);
index++;
root.leftNode = build(array);
index++;
root.rightNode = build(array);
return root;
顺着先序遍历的顺序,从根节点开始递归创建节点,直到叶子结点 '# ',从叶子节点向上递归赋值,从而形成树的结构.
注意事项:
index值使用static关键字修饰,在面对多个样例的时候,要注意将其值重新赋值为0;
先序遍历可以使使用标号来判断位置(待补)
二叉树的分层遍历
在线OJ
和普通的分层遍历不同的是,这道题返回值要求使用List<List>结构作为返回值.所以在遍历过程中,我们需要进行销量小量的修改
/**
* 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 {
static List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
result = new ArrayList<>();
if(root == null){
return result;
}
cengadd(root,0);
return result;
}
private void cengadd(TreeNode root,int index) {
//防止数组越界
if(index == result.size()){
result.add(new ArrayList<>());
}
//层序遍历
result.get(index).add(root.val);
if(root.left!=null){
//使用index +1 来控制层数.
cengadd(root.left,index+1);
}
if(root.right !=null){
cengadd(root.right,index+1);
}
}
}
使用static关键字进行修饰index变量.增加一个方法添加参数index.
使用index来控制每层使用的ArrayList对象.然后就和普通的层序遍历没什么区别了.
注意事项:
在每次递归的时候,如果index 层数和当前外层list的元素个数,也就是存放每层元素的ArrayLIst数量相同.因为index使用的时候是取下标操作也就是代表着,此时没有这个内层AyyayList对象进行操作.所以要进行添加新的AyyayList对象
/**
* 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 List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if(root == null){
return list;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
ArrayList<Integer> list2 = new ArrayList<>();
int size = queue.size();
//每次循环遍历一层的所有元素
//将其添加到这一层的ArrayList中并且将下一层非空子树添加到队列
for (int i = 0; i < size; i++) {
TreeNode poll = queue.poll();
list2.add(poll.val);
if (poll.left != null) {
queue.offer(poll.left);
}
if (poll.right != null) {
queue.offer(poll.right);
}
}
list.add(list2);
}
return list;
}
}
和上一个代码不同的地方在于,使用了队列存放将要遍历的节点.使用for循环 ,将一层的每个节点的值add入内层ArrayList对象中.并将每一个节点的左右非空子树存入队列.方便下次遍历.
注意事项 : 在使用循环 for (int i = 0; i < size; i++) {的时候,要注意size不能使用queue.size();
因为在代码运行过程中.它的值是会不断变换.会导致循环不能正常结束.
最近公共祖先
在线OJ
题目描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
static TreeNode lca = null;
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
dfs(root, p, q);
return lca;
}
private static boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return false;
}
int mid = (root == p || root == q) ? 1 : 0;
int leftok = dfs(root.left, p, q) ? 1 : 0;
int rightok = dfs(root.right, p, q) ? 1 : 0;
if (mid + leftok + rightok == 2) {
lca = root;
}
return (mid + leftok + rightok) >0;
}
}
题目分析:
公共祖先包括什么:
在上图中 7 这个节点的祖先是: 7 2 5 3 这四个节点.
怎么能找到两个节点的公共祖先呢,我们可以分开进行查找
int mid = (root == p || root == q) ? 1 : 0;
int leftok = dfs(root.left, p, q) ? 1 : 0;
int rightok = dfs(root.right, p, q) ? 1 : 0;
- 判断当前节点是否为 p或者q.
- 判断左子树是否有 p或者q.
- 判断左子树是否有 p或者q.
当满足条件(mid + leftok + rightok == 2)的时候.有6种情况
- 当前节点为p ,他的左子树为q
- 当前节点为q ,他的左子树为p
- 当前节点为p ,他的右子树为q
- 当前节点为q ,他的左子树为p
- 当前节点的左子树为p,右子树为q
- 当前节点的左子树为q,右子树为p
此时将当前节点进行修改.
此时这个节点便是我们要找的公共祖先.
二叉搜索树与双向链表(未理解)
在线OJ
题目描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
二叉搜索树
对于任意节点来说,左子树节点小于根节点,右子树节点大于根节点.
二叉搜索树的意义便是查找.
题目分析:
观察双向链表和树节点的结构发现有相似之处
所以我们可以有这样的思路:
中序遍历
- 递归的把左子树转成双向链表.
- 把根节点尾插到左子树的链表末尾.
- 再递归的把右子树转成双向链表
- 把根节点头插到右子树的链表前面
public static TreeNode Convert(TreeNode pRootOfTree) {
// 判定特殊情况
if (pRootOfTree == null) {
return null;
}
if (pRootOfTree.left == null && pRootOfTree.right == null) {
return pRootOfTree;
}
// 处理一般情况
// 1. 先递归的把左子树转成链表
// 得到的 leftHead 可能是 null, 下面在使用的时候要考虑到这个细节
TreeNode leftHead = Convert(pRootOfTree.left);
// 2. 把根节点尾插到 leftHead 这个链表中
// 需要找到 leftHead 的末尾节点才能尾插
TreeNode leftTail = leftHead;
while (leftTail != null && leftTail.right != null) {
leftTail = leftTail.right;
}
if (leftHead != null) {
leftTail.right = pRootOfTree;
pRootOfTree.left = leftTail;
}
// 3. 递归的转换右子树了
TreeNode rightHead = Convert(pRootOfTree.right);
// 4. 把当前节点头插到右侧链表的前面
if (rightHead != null) {
pRootOfTree.right = rightHead;
rightHead.left = pRootOfTree;
}
// 需要返回这个最终链表的头结点.
// 注意, leftHead 可能是空链表. 如果是空链表
// 整体的头结点就应该是 pRootOfTree 了
return leftHead != null ? leftHead : pRootOfTree;
}
根据一棵树的前序遍历与中序遍历构造二叉树。
/**
* 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;
* }
* }
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
class Solution {
public static int index = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
index = 0;
List<Integer> list = new ArrayList<>();
for (int i :
inorder) {
list.add(i);
}
return _buildTree(preorder, list);
}
private TreeNode _buildTree(int[] preorder, List<Integer> list) {
if (list.isEmpty()) {
return null;
}
if (index >= preorder.length) {
return null;
}
//创建节点
TreeNode root = new TreeNode(preorder[index]);
//确定在中序结果中的位置
int pos = list.indexOf(root.val);
index++;
root.left = _buildTree(preorder, list.subList(0, pos));
root.right = _buildTree(preorder, list.subList(pos + 1, list.size()));
return root;
}
}
部分递归过程如上图:主要思路就是,依据先序遍历顺序,然后再中序结果中查找相应位置从而确定节点位置.因为在每次递归的过程中.list集合都会被依照左右子树从而分解.每次递归的list结果越短,当list集合为空的时候就到达叶子结点
根据一棵树的中序遍历与后序遍历构造二叉树([课堂不讲解,课后完成作业])。
/**
* 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 static int index = 0;
public static TreeNode buildTree(int[] inorder, int[] postorder) {
index = postorder.length-1;
List<Integer> list = new ArrayList<>();
for (int i :
inorder) {
list.add(i);
}
return _buildTree(postorder,list);
}
private static TreeNode _buildTree(int[] postorder, List<Integer> list) {
if(list.isEmpty()){
return null;
}
if(index < 0){
return null;
}
TreeNode root = new TreeNode(postorder[index]);
int pos = list.indexOf(root.val);
index--;
root.right = _buildTree(postorder,list.subList(pos+1,list.size()));
root.left = _buildTree(postorder,list.subList(0,pos));
return root;
}
}
思路基本和上一个题相同,但是由于后序遍历的原因,我们需要从后续结果的末尾进行遍历,并且对其递归顺序和进行修改.先递归右子树,后递归左子树.
二叉树创建字符串
在线OJ
写题的时候死活想不出来,这个递归是怎么写的.
public static String tree2str(TreeNode t) {
//如果当前节点为空
if(t==null)
return "";
//左右子树为空.
if(t.left==null && t.right==null)
return t.val+"";
//右子树为空,左子树不为空
if(t.right==null)
return t.val+"("+tree2str(t.left)+")";
return t.val+"("+tree2str(t.left)+")("+tree2str(t.right)+")";
//如果当前节点有左右子树,在两个子树的结果外加上一个( 左子树 右子树 )
}
查看题解后发现
还是不会!
官方题解分了四种情况
- 当前节点有两个孩子的时候,需要在两个孩子的结果的外层都加上一层
- 当前节点没有孩子,就不需要在节点上面加上任何括号.
- 如果节点只有左节点,递归过程中,只需要在左孩子的结果外加上一层括号
- 如果只有右孩子,我们不仅需要 () 表示左孩子为空并且也要对右孩子加括号
分析这四种情况,我们可以对判断顺序进行调整.
- 先判断当前节点是否为空节点
返回空字符串 - 接着判断是不是没有子树的节点.
返回数字即可,不需要括号 - 如果进行到第三步,那么肯定有子树,我们就 判断是否为只有左子树的情况
如果有只左子树,我们输出左孩子当前节点的结果 并加上 “(” +递归调用方法返回左子树的其他结果 + “)” - 只有右子树和两个孩子的情况在处理上都相同,要拼接结果和 “(” + 左子树的返回结果 + “)(” + 右子树的返回结果+")"
public static StringBuilder builder = new StringBuilder();
public static String tree2str(TreeNode t) {
if(t == null){
return "";
}
helper(t);
//第一个节点不需要括号包括
builder.deleteCharAt(0);
builder.deleteCharAt(builder.length()-1);
return builder.toString();
}
private static void helper(TreeNode root) {
if(root == null){
return;
}
builder.append("(");
builder.append(root.val);
helper(root.left);
//左节点为空,且有右节点.要增加个()表示左子树为空
if(root.left == null && root.right != null){
builder.append("()");
}
//递归右子树
helper(root.right);
builder.append(")");
}
因为整体要使用先序遍历的顺序
这种思路会更简单点,使用StringBuilder来拼接字符串遇到节点
- 首先添加一个( val
- 然后判断是否有左子树进行递归.
- 如果遇到子节点为空右节点存在的情况,需要拼接一个空()来表示左子树为空
- 然后对右子树进行递归
- 最后补上右括号
但是开头第一个节点不需要()包括所以我们需要对builder的开始和末尾进行删除.