1、前言
数组、链表、二叉树、动态规划、栈与队列是面试中常考的知识点,而在这几个知识点中,二叉树属于难度比较大的部分,因此在这里总结一下二叉树常考的操作,包括:
(1)二叉树的递归遍历;
(2)二叉树的非递归遍历;
(3)求二叉树叶子节点的个数;
(4)求二叉树的深度;
(5)在二叉树中查找某颗子树;
(6)求二叉树中和为某个值的路径;
(7)求二叉树中的最大路径和;
(8)二叉树的镜像;
(9)判断某个序列是否是二叉搜索树的后序遍历序列;
(10)二叉搜索树和双向链表的相互转换;
(11)树中两个结点的最近祖先;
(12)二叉树搜索树的插入与删除;
(13)堆的插入与删除;
(14)判断一棵树是不是平衡二叉树;
(15)平衡二叉树的调整;
(16)判断一棵二叉是否对称;
(17)层次遍历一棵二叉树;
2、二叉树的基本操作
(9)判断某个序列是否是二叉搜索树的后序遍历序列
基本思路:可以采用倒推的方法,如果一个序列是二叉搜索树的后序遍历序列,那么该序列会有什么样的特征?
如果一个序列是二叉树的后序遍历序列,那么它一定具有以下性质:
(1)序列可以根据序列的最后一个数number划分为两部分,一部分大于number,记为A;另一部分小于number,记为B;且A一定在B的前面,A和B中的长度可以为0;
(2)序列A、B同样遵循性质(1);
def IsBackOrderSeq(seq,start,end):
if len(seq) <= 0:
return False
length = end - start + 1
if length == 1:
return True
greater_index = start
for i in range(start,end):
if seq[i] < seq[end]:
greater_index += 1
for j in range(greater_index,end):
if seq[j] < seq[end]:
return False
front_isback = True
back_isback = True
if greater_index - 1 >= start:
front_isback = IsBackOrderSeq(seq,start,greater_index - 1)
if greater_index <= end - 1:
back_isback = IsBackOrderSeq(seq,greater_index,end - 1)
return front_isback and back_isback
(10)二叉搜索树和双向链表的相互转换
输入一棵二叉排序树,将该二叉排序树转换成排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
如下图所示:
基本思路:我们把二叉树搜索树中每个节点的左指针作为双向链表中的前向指针,右指针看成后向指针。同时将二叉树分为三个部分,左子树,根节点,和右子树。在转换时,只需将根节点与左子树中的最大节点链接,然后再将根节点与右子树的最小节点连接。
具体地,使用指针pLasNodeInList来保存已经转换好的排序双向链表的尾节点,再使用中序遍历二叉树中的每一个节点时,不断地将当前遍历节点插入到双向链表的末尾。
简单来说,就是不断地在排序的双向链表末尾pLastNodeInList插入中序遍历的当前节点。
代码实现:
pLastNodeInList = None
def ConvertNode(rootnode):
if rootnode == None:
return
ConvertNode(rootnode.left)
global pLastNodeInList
rootnode.left = pLastNodeInList
if pLastNodeInList != None:
pLastNodeInList.right = rootnode
pLastNodeInList = rootnode
ConvertNode(rootnode.right)
(11)树中两个结点的最近祖先
基本思想:
利用后序遍历实现;
对于当前遍历节点curnode,如果当前节点为null或等于A、或等于B,直接返回该节点;
先处理curnode的左右子树,左子树返回left,右子树返回right,判断left和right;
1)如果left、right都不为空,则说明在curnode的左子树中找到了A或B,curnode的右子树中也找到了A或B,curnode为A和B的首次相遇点,也是A和B的最近祖先,直接返回curnode;
2)如果left和right中一个为null,另一个不为null,说明不为空的节点是A和B中的一个,直接返回不为空的节点;
3)如果left、right都为空,说明以curnode为根的子树上没有发现A或B,返回null;
代码实现:
def getLatestAncestor(rootnode,node1,node2):
if rootnode == None or rootnode.val == node1.val or rootnode.val == node2.val:
return rootnode
left = getLatestAncestor(rootnode.left,node1,node2)
right = getLatestAncestor(rootnode.right,node1,node2)
if left != None and right != None:
return rootnode
if left != None:
return left
if right != None:
return right
return None
(12)二叉树搜索树的插入与删除;
(13)堆的插入与删除;
(14)判断一棵树是不是平衡二叉树;
(15)平衡二叉树的调整;
关于上述问题,具体参考:通用算法 - [树结构] - 特殊的树结构
(16) 判断二叉树是否对称
例如:二叉树[1,2,2,4,3,3,4]
是对称的:
1
/ \
2 2
/ \ / \
3 4 4 3
而二叉树[1,2,2,null,3,null,3]
则不是镜像对称的:
1
/ \
2 2
\ \
3 3
基本思路:
如果一棵二叉树是对称的,那么它的左右子树成镜像对称;
接下来问题转化成如何判断两棵树成镜像对称,
经过分析(如下图所示),两棵树成镜像对称需要满足以下GAN个条件:
(1)两棵树的根节点相同;
(2)其中一棵树的右子树和另一棵树的左子树成镜像对称;
(3)其中一棵树的左子树和另一棵树的右子树成镜像对称;
由上述条件,很容易利用递归的方法来解决判断两棵二叉树是否成镜像对称的问题。
代码实现:
bool twotreeisSymmetric(TreeNode* tree1, TreeNode* tree2){
if(tree1 == NULL || tree2 == NULL){
if(tree1 == NULL && tree2 == NULL){
return true;
}
else{
return false;
}
}
return (tree1->val == tree2->val &&
twotreeisSymmetric(tree1->right,tree2->left) &&
twotreeisSymmetric(tree1->left, tree2->right));
}
算法分析:
时间复杂度:因为我们遍历了整棵树一次,故算法的时间复杂度为
O
(
n
)
O(n)
O(n),其中
n
n
n为树的节点数量。
空间复杂度:空间复杂度与递归调用的次数有关,而递归调用的次数受限于树的高度,在最糟糕的情况下,树是线性的,即高度为
n
n
n,所以该方法的空间复杂度为
O
(
n
)
O(n)
O(n)。
(17)层次遍历二叉树
基本思路:
层次遍历的思想很简单,使用一个辅助队列即可完成,每次从队首取出一个节点,保存该节点的值,然后将该节点的孩子节点放入队尾,直到列队为空。
如果要得到的不仅仅是节点的值,还包括该节点在树中的层次信息,那么需要使用一个二元组<节点,层数>来保存节点和节点的层数信息,队列中的每个元素都是这样的二元组。将一个节点入栈时,它的层数等于它父节点(即取出的队首元素的)的层数加1。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
#include<cmath>
#include<tuple>
#include<algorithm>
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> nullResult;
if(root == NULL){
return nullResult;
}
queue<tuple<TreeNode*,int> > q;
q.push(make_tuple(root,0));
vector<tuple<int,int> > temp;
int maxlayer = 0;
while(!q.empty()){
TreeNode* curnode = get<0>(q.front());
int layer = get<1>(q.front());
if(layer > maxlayer){
maxlayer = layer;
}
if(curnode!= NULL){
temp.push_back(make_tuple(curnode->val,layer));
}
if(curnode->left != NULL){
q.push(make_tuple(curnode->left,layer + 1));
}
if(curnode->right != NULL){
q.push(make_tuple(curnode->right, layer + 1));
}
q.pop();
}
vector<vector<int>> normalResult(maxlayer + 1);
for(int i = 0; i < temp.size(); i++){
int value = get<0>(temp[i]);
int layer = get<1>(temp[i]);
normalResult[layer].push_back(value);
}
return normalResult;
// queue<TreeNode*> nodeq;
// queue<int> layerq;
// vector<int> valueseq;
// vector<int> layerseq;
// nodeq.push(root);
// layerq.push(0);
// while(!nodeq.empty()){
// TreeNode* curnode = nodeq.front();
// int layer = layerq.front();
// if(curnode != NULL){
// valueseq.push_back(curnode->val);
// layerseq.push_back(layer);
// }
// if(curnode->left != NULL){
// nodeq.push(curnode->left);
// layerq.push(layer + 1);
// }
// if(curnode->right != NULL){
// nodeq.push(curnode->right);
// layerq.push(layer + 1);
// }
// nodeq.pop();
// layerq.pop();
// }
// vector<int>::iterator maxlayer = max_element(layerseq.begin(),layerseq.end());
// vector<vector<int>> normalResult(*maxlayer + 1);
// for(int i=0; i < valueseq.size(); i++){
// normalResult[layerseq[i]].push_back(valueseq[i]);
// }
// return normalResult;
}
};