【牛客BAT算法】6.二叉树

目录

二叉树类型的题目为常考题型

案例一:用递归方式和非递归式实现二叉树的先序、中序和后序的遍历打印。

先序遍历:

中序遍历:

后序遍历:

案例二:给定二叉树的头结点head,要求按照以下格式打印

案例三:二叉树的序列化与反序列化

案例四:判断一棵树是否为平衡二叉树

案例五:判断一棵树是否是搜索二叉树

案例六:判断一棵树是否是完全二叉树

案例七:求二叉树节点的后继节点

案例八:折纸对折N次,从上到下打印所用折痕的方向

案例九:找出搜索二叉树中的错误节点

案例十:节点间的最大距离

案例十一:最大搜索二叉子树


二叉树类型的题目为常考题型

1.能够结合队列,栈,链表,字符串等数据结构。

2.需要掌握图的基本遍历方式,比如BFS和DFS。
3.需要掌握递归函数的使用,并自己设计出递归过程。
4.与实际工作结合紧密。
 
在面试中,二叉树节点类型仅包括:数据项,左孩子,右孩子
工程上的二叉树节点类型,往往多一条指向父节点的指针。
一般默认面试中的二叉树节点结构不包含指向父节点的指针,除非特殊说明。
 
二叉树的子树
二叉树的子树:以任何一个节点为头部的整棵树为二叉树的子树
平衡二叉树
搜索二叉树
满二叉树,完全二叉树
 

案例一:用递归方式和非递归式实现二叉树的先序、中序和后序的遍历打印。

先序遍历:

递归
非递归
1.申请一个栈stack
2.将头结点head压入栈中
3.每次从stack中弹出栈顶节点,记为cur,打印cur节点的值;
   如果cur的右孩子不为空,将cur的右孩子压入栈中;
   如果cur的左孩子不为空,将cur的左孩子压入栈中
4.不断重复3,直到stack为空,过程结束
 

中序遍历:

递归
非递归
1.请一个栈,记为stack,申请一个变量cur,初始时令cur等于头结点
2.先把cur节点压入栈中,对以cur节点为头的整颗子树来说,依次将整颗子树的左边界压入栈中,即不断cur=cur.left,然后重复步骤2
3.不断重复步骤2,直到cur为空,此时从stack中弹出一个节点,记为node,打印node的值,并让cur=node.right,然后继续重复步骤2
4.当stack为空时并且cur也为空时,过程结束
 

后序遍历:

递归
非递归
方法1:使用两个栈实现
(其实就是改写了先序遍历,将根左右改为了根右左,然后利用栈,将顺序变为了左右根,即为后序遍历
1.申请一个栈,记为s1,然后将头结点压入s1中
2.从s1中弹出的节点记为cur,然后先把cur的左孩子压入s1中,再把cur的右孩子压入s1中
3.在整个过程中,每一个从s1中弹出的节点都放进第二个栈s2中
4.不断重复步骤2和步骤3,直到s1为空,过程停止
5.从s2中一次弹出节点并打印,打印的顺序就是后序遍历的顺序
方法2:使用一个栈实现
1.申请一个栈,记为stack,将头结点压入stack,同时设置两个变量h和c,在整个过程中,h代表最近一次弹出并打印的节点,c代表当前stack的栈顶节点,初始时令h为头结点,c为null
2.每次令c等于当前stack的栈顶节点,但是不从stack中弹出节点,此时分为以下三种情况
   (1)如果c的左孩子不为空,并且h不等于c的左孩子,也不等于c的右孩子,则把c的左孩子压入stack中
(注:如果h等于c的左孩子或者h等于c的右孩子,都说明c的左子树已经打印完毕
否则,需将c的左孩子压入栈
   (2)如果情况1不成立,并且c的右孩子不为空,并且h不等于c的右孩子,则把c的右孩子压入stack中
(注:如果h等于c的右孩子,说明c的右子树已经打印完毕,
否则,需将c的右孩子压入栈)
   (3)如果情况1和情况2都不成立,那么从stack中弹出c并打印,然后令h等于c
(注:情况1和2都不成立,说明c的左子树和右子树都已经打印完毕)
注意以上3步是,if else else的关系
3.一直重复步骤2,直到stack为空,过程停止
以上不管是递归方法还是非递归方法,遍历整棵树的时间复杂度都为O(N),N为二叉树的节点个数,额外空间复杂度为O(L),L为二叉树的层数
 

案例二:给定二叉树的头结点head,要求按照以下格式打印

(格式为:每打印二叉树的一层,换行,层间从左到右遍历)
答案:
定义两个变量last和nlast
last:表示正在打印的当前行的最右节点
nlast:表示下一行的最右节点
每层都做从左到右的宽度优先遍历,如果遍历到last节点,说明该换行了,换行后令last=nlast
如何更新last和nlast,让nlast一直跟踪遍历过程中最新加入的节点即可
 

案例三:二叉树的序列化与反序列化

1.二叉树--->字符串(序列化)
2.字符串--->二叉树(反序列化)
序列化的方式
1.根据先序遍历序列化
2.根据中序遍历序列化
3.根据后序遍历序列化
4.层次遍历序列化
给定一棵二叉树的头结点为head,并已知二叉树节点值类型为32位整型,设计一种二叉树序列化和反序列化的方案
答案:
先序遍历对二叉树进行序列化
1.假设序列化结果为str,初始为空
2.先序遍历二叉树时如果遇到空节点,str末尾添加“#!”
3.如果遇到不为空的节点,假设节点值为3,在str末尾添加“3!”
注意点:
1.#代表空节点,每个节点后添加结束符!(如果不添加结束符,会产生歧义,如12,是表示1,2还是12?)
2.用什么遍历方式序列,就要用什么方式反序列化(先序序列化,反序列化时就要用先序创建二叉树)
 
 

案例四:判断一棵树是否为平衡二叉树

给定一棵树的头结点head,判断这棵树是不是平衡二叉树
答案:
public boolean isBalance(Node head) {
    boolean[] res = new boolean[1];
    res[0] = true;
    getHeight(head, 1, res);
    return res[0];
}


public int getHeight(Node head, int level, boolean[] res) {
    if (head == null) {
        return level;
    }
    int LH = getHeight(head.left, level + 1, res);
    if (!res[0]) {
        return level;
    }
    int RH = getHeight(head.right, level + 1, res);
    if (!res[0]) {
        return level;
    }
    if (Math.abs(LH - RH) > 1) {
        res[0] = false;
    }
    return Math.max(LH, RH);
}

首先遍历左子树,收集两个信息

1. 左子树是否为平衡二叉树(true or false)

2. 左子树最多到哪一层, LH

如果左子树不是平衡二叉树,直接返回false。

否则遍历右子树,收集两个信息

1.右子树是否为平衡二叉树

2.右子树最多到哪一层, RH

如果右子树不是平衡二叉树,直接返回false。

此时,左右子树都是平衡二叉树,

考察LH,RH的差值是否大于1

大于1,不是平衡二叉树,返回false

不大于1,返回左右子树深度的最大值。

 

案例五:判断一棵树是否是搜索二叉树

给定一棵二叉树的头结点head,判断这棵树是否是搜索二叉树

答案:

1.改写二叉树的中序遍历

2.遍历到每个节点的值时,如果一直比上一个遍历的节点值要大,则是搜索二叉树,否则不是搜索二叉树

3.为了方便同时得到当前节点,和上一个遍历的节点,二叉树中中序遍历使用非递归比较合适。

 

案例六:判断一棵树是否是完全二叉树

给定一棵树的头结点head,判断一棵树是否是完全二叉树

答案:

1.采用层次遍历方式,从每层的左边向右边依次遍历所有节点

2.如果当前结果有右孩子,但没有左孩子,直接返回false

3.如果当前节点并不是左右孩子全有,那之后的节点必须都为叶节点,否则返回false

4.遍历过程中如果不返回false,遍历结束后直接返回true

 

案例七:求二叉树节点的后继节点

给定新的节点类型,构成新的二叉树,每个节点的parent指向父节点,头结点的parent指向空。

给定二叉树中的某个节点node,该节点并不一定是头结点,可能是树中任何一个节点。

实现返回node的后继节点的函数。

public class Node {
    int value;
    Node left;
    Node right;
    Node parent;
    public Node(int data) {
        this.value = data;
    }
}

后继节点与前驱节点

后继节点:一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点。

前驱节点:一个节点的前驱节点是指,这个节点在中序遍历序列中的上一个节点。

 

答案:

普通方法:时间复杂度O(N),空间复杂度O(N)

1.通过node的parent不断向上找到头结点

2.通过找到的头结点,进行中序遍历,生成中序遍历序列

3.中序遍历序列中,node节点的下一个节点,就是后继节点

最优解:(其实就是分析中序遍历)

如果node节点和node后继节点之间的实际距离为L,最优解只用走过L个节点,

时间复杂度为O(L),空间复杂度为O(1)

具体过程:

情况1:如果node节点有右子树,node节点的后继节点为右子树上最左边的节点

情况2:如果node节点没有右子树,那个先看node是不是node头结点的左孩子,

如果是左孩子,node的父节点就是node的后继节点

如果是右孩子,就向上找node的后继节点,假设向上移动到的节点记为s,s的父节点记为p,如果发现s是p的左孩子,那么节点p就是node的后继节点,否则一直向上移动

 

案例八:折纸对折N次,从上到下打印所用折痕的方向

折纸,每次从下向上折

对折一次,从上到下产生的折痕为下

对折两次,从上到下产生的折痕为下下上

对折三次,从上到下产生的折痕为.............

给定对折次数N,从上到下打印所有折痕的方向

答案:

折痕方向构成了一棵树

                                 下

                                /    \

                            上       下

                           /    \      /   \                   

                        上    下 上    下

                (下方向).........................(上方向)

(右,根,左)的遍历顺序,就是所有折痕的打印顺序。

 

案例九:找出搜索二叉树中的错误节点

一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树。

要求找出这两个错误节点。

答案:

1.对二叉树进行中序遍历,依次出现的节点会一直升序,如果两个节点的位置错了,一定会出现降序

2.第一个错误节点为第一次降序的比较大的节点,第二个错误节点为最后一次降序时较小的节点

3.改写二叉树的中序遍历即可

【分析如下:

1.如果在中序遍历时节点值出现了两次降序,

第一个错误的节点为第一次降序时较大的节点

第二个错误的节点为第二次降序时较小的节点

如,原为1,2,3,4,5,交换2,5后变为为1,5,3,4,2

2.如果在中序遍历时只出现了一次降序

第一个错误的节点为降序时较大的节点

第二个错误的节点为降序时较小的节点

如,原为1,2,3,4,5,交换3,4变为1,2,4,3,5】

 

案例十:节点间的最大距离

从二叉树的节点A出发,可以向上走或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫做A到B的距离,比如

节点4和节点2的距离为2,节点5和节点6的距离为5,给定一棵二叉树的头结点head,求整棵树上节点间的最大距离

 

答案:

一个以h为头的树上,最大距离只可能来自以下三种情况:

情况1:h的左子树上的最大距离(左子树)

情况2:h的右子树上的最大距离(右子树)

情况3:h左子树上离h左孩子最远的距离,加上h自身这个节点,再加上h右子树上离h右孩子的最远距离。(分别来自左右子树)

三个值中最大的值就是以h为头的整棵树上最远的距离

具体过程:

1.整个过程为后序遍历,在二叉树的每颗子树上都执行步骤2

2.假设子树头结点为h,

处理h的左子树,得到两个信息,左子树上的最大距离记为Lmax1,左子树上距离h左孩子的最远距离记为Lmax2.

处理h的右子树,得到两个信息,右子树上的最大距离记为Rmax1,右子树上距离h右孩子的最远距离记为Rmax2

跨h节点的情况下的最远距离为Lmax2+1+Rmax2,

这个值与Lmax1和Rmax1比较,最大值为以h为头的最大距离

3.Lmax2+1就是h左子树上距离h最远的点到h的距离

Rmax2+1就是h右子树上距离h最远的点到h的距离,

选两者中最大的一个作为h树上距离h最远的距离返回

4.用返回长度为2的数组的方式,可以做到返回两个值

 

案例十一:最大搜索二叉子树

给定一棵二叉树的头结点head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这颗子树的头结点。例如,右边的树为左边的数的最大二叉子树。

答案:

---------------------------------------------------------------------------------------------------

我想到的一种方法,利用二叉树的中序和先序遍历

1.先求二叉树的中序遍历:0,1,3,6,2,4,5,10,11,14,15,12,20,13,16

2.在中序遍历中找最长的递增序列,记为A,为2,4,5,10,11,14,15,找到根即可

3.对二叉树进行先序遍历,遍历过程中,看是否当前节点是否在A中,是,找到了根,返回即可

该想法未经验证是否可行,而且时间复杂度有点高O(N^2),N为二叉树的节点个数

---------------------------------------------------------------------------------------------------

以节点node为头的树中,最大的搜索二叉子树只可能来自以下两种情况:

1.来自node左子树上的最大搜索二叉子树是以node左孩子为头的,

并且来自node右子树上的最大搜索二叉子树是以node右孩子为头的,

node左子树上的最大搜索二叉子树的最大值小于node的节点值,

node右子树上的最大搜索二叉子树的最小值大于node的节点值,

那么以节点node为头的整棵树都是搜索二叉树

2.如果不满足第一种情况,说明以节点node为头的树整体不能练成搜索二叉树。

这种情况下,以node为头的树上的最大搜索二叉子树是

来自node的左子树上的最大搜索二叉子树和

来自node的右子树上的最大搜索二叉子树之间,节点数较多的那个

具体过程:

1.整体过程是二叉树的后序遍历

2.遍历到当前节点记为cur时,

先遍历cur的左子树并收集4个信息,分别是左子树上最大搜索二叉子树的头结点、节点数、树上最小值和树上最大值,

再遍历cur的右子树并收集4个信息,分别是右子树上最大搜索二叉子树的头结点、节点数、最小值和最大值

3.根据步骤2所收集的信息,判断是否满足第一种情况,如果满足,返回cur节点;

如果满足第二种情况,就返回左子树和右子树各自的最大搜索二叉子树中,节点数较多的那个树的头结点

4.对于如何返回4个信息,可以使用全局变量更新的方式或长度为4的数组的方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值