内容会持续更新,有错误的地方欢迎指正,谢谢!
前言
宏观概念:有向图 可退化为 树 可退化为 链表
二叉树虽然简单,但是与链表相比,树的题目涉及到大量指针,细微之处更能特别考验程序员的能力,所以,若想加大面试难度,树的题目是不二之选,博主在此列出一些二叉树常见的题型。
思路:只有画出二叉树的图+具体的例子(一边看着图形一边讲解,面试官会更轻松地理解你的思路,并觉得你好沟通),才好找出题目的规律,才好发现错误和漏洞,才好设计出可行的算法。也就是,先想(讲)清楚思路,得到肯定后,再码代码!
注意:先考虑边界测试:每一次需要访问地址的时候都要问自己这个地址有没有可能是nullptr,如果是nullptr那怎么处理;再考虑功能测试:完全二叉树和非完全二叉树。
用递归实现的话,边界测试一般需要以下判断:
if(pRoot==nullptr)
return;//或return 0; 或return nullptr;
小套路:如果递归函数为bool型,则双重递归函数要么在if中,要么就在return中。例子:
if(Func(pRoot->left)&&Func(pRoot->right))
return true;
return false;
//等价于:
return Func(pRoot->left)&&Func(pRoot->right);
//但是,第一种可以加一些处理操作:
if(Func(pRoot->left)&&Func(pRoot->right))
{
//可以加一些cout用于输出,或者加一些if用于判断是否返回true等等
return true;
}
return false;
目录
1、前序遍历,中序遍历,后序遍历
法一:递归实现(其实递归就是一种栈)
法二:栈+迭代
2、层次遍历
宽度优先搜索BFS,迭代,使用先进先出的队列queue或两端都可以进出的deque,要让队列有进有出。
3、求树的结点数
遍历。return 1+左子树的个数+右子树的个数
4、求树的叶子数
遍历。return 左子树的叶结点数+右子树的叶结点数
5、求二叉树第k层的结点个数
遍历。k作为递归函数的参数,每降低一层,k就要-1。
return KNodeCount(pRoot->left,k-1)+KNodeCount(pRoot->right,k-1);
13题(二叉树中和为某一值的路径)的思路和该题很像。
6、求树的深度
遍历。对根结点求深度,就是求其左右子树的深度中最大的那个深度+1
7、判断两颗二叉树是否结构相同
(不用考虑val是否相同。结构相同:两颗二叉树的两个根结点对应的的左子树和右子树结构相同。)
遍历。return 判断两棵树的左子树是否相同&&判断两棵树的右子树是否相同;
什么叫相同?两个对应的结点同时为空
什么叫不同?两个对应的结点中只有一个结点为空
那都不为空时呢?去递归呗。
8、求二叉树的镜像(又叫翻转二叉树)
遍历。对于根结点,交换其左右子树,左右子树各自去递归。
补充的问题:判断是不是对称的二叉树。方法:bool函数,前序遍历,对应的结构和对应的值都要相等,return里 左右子树继续去递归。
小窍门:该类问题很抽象,图形能使抽象的问题具体化、形象化,得到规律。比如:二叉树、链表、二维数组等问题都可以采用画图的方法来分析。空想很难能想明白题目中隐含的规律。
9、求两个结点的最低公共祖先结点
题目1:若这是一棵二叉查找树,那么就简单了:1.当根结点的值大于两个这结点的值,则继续在左子树中找;2.当根结点的值小于两个这结点的值,则继续在右子树中找;3.当根结点的值在这两个结点的值之间,则该根结点就是最低公共祖先结点。
题目2:若这不是一棵二叉树,只是普通的树,每个结点除了根结点外 都有指向父结点的指针:这不就是求两个链表的第一个公共结点嘛。
题目3:就是下方的第9题,若这是一颗二叉树(题目偏难),则遍历后,需有如下判断:
//对一个根结点来说,分别在左右子树中找到了目标结点,则该根结点就是所求结点。
if (left!=nullptr && right!=nullptr)
return pRoot;
//对一个根结点来说,只在左子树或右子树找到了目标结点,则返回找到的结点。
return left!=nullptr ? left : right;
10、求任意两结点距离
遍历。先找到两个结点的最低公共祖先结点,然后分别计算最低公共祖先结点(根结点)与它们的距离,最后相加即可。也就是两个函数,第二个函数用于求距离:遍历,先在左子树中找,if没找到的话,再在右子树中找,找到了则return res+1;最后都没找到就return -1。
11、找出二叉树中某个结点的所有祖先结点
遍历。bool递归函数。if(找出该结点的左子树中某个结点的所有祖先结点||找出该结点的右子树。。。),如果为if条件为true,则输出该结点。
12、判断二叉树是否是平衡二叉树(了解)
遍历。if(判断左子树是否是平衡二叉树&&判断右子树是否是平衡二叉树),再在这个if中,if(判断深度差left-right的绝对值是否小于1)。。。挺有挑战的一道题。
13、二叉树中和为某一值的路径
前序遍历,和expectNumber作为递归函数的一个参数,每降低一层,expectNumber就要减去刚才那个结点的值。
定义一个二维vector容器res和一个一维vector容器temp。每路过一个结点,就将该结点装进temp。若走到了叶节点,且该条路的和刚好等于expectNumber,则将这条路(temp记录的)装入res;若此时和不等于expectNumber,就回溯。
如何回溯?
在双重递归后加上temp.pop_back() 弹出最后那个元素,再装入下一个要遍历的元素,不就好了。
14、根据前序和中序重建二叉树
构建 左子树的前序中序 和 右子树的前序中序,再分左右子树递归。
小窍门:当我们发现大问题和小问题在本质上是一致时,便可用递归解决,所以二叉树的题基本都用递归。由于要遍历二叉树,所以基本都用双重递归。
15、二叉搜索树与双向链表
中序遍历。简单题。定义两个变量,一个变量用于记录头结点,一个变量用于改变指向。
小窍门:一定要先画图分析,通过分析得到思路后,再动手写代码,不要一开始就写,不然越写越懵。
1、前序遍历,中序遍历,后序遍历
要么用递归(递归其实就是栈的思想),要么就用栈+迭代。
其实,其他题目都是在间接地考察这三种遍历,尤其是前序遍历考得多。所以,该题是核心。
前序遍历-根左右