有需要文档的同学可以私聊我,我做成了文档,可以共享,没有时间好好的写成一篇比较好的博客
二叉树面试题总结
一:基本概念:
1:普通二叉树特点:
1:n0=n2+1(n0表示度为0的结点,n2表示度为2的结点);
2:结点个数:n0+n1+n2;
3:二叉树的第 i 层至多有 2^i−1 个结点;
4:深度为 k 的二叉树至多有 2^k−1 个结点;
5:前序遍历规则:根左右;
6:中序遍历规则:左根右;
7:后序遍历规则:左右根;
8:层序遍历,从根节点开始,同一层中按照先左子树再按右子树的次序遍历二叉树。
2:二叉搜索树的特点
1:二叉搜索树又称二叉排序树,它是一颗空树,或者具有以下三个特点(2,3,4);
2:若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
3:若它的右子树不为空,则右子树上的所有节点的值都大于根节点的值;
4:它的左右子树也分别为二叉搜索树;
3:满二叉树概念和特点
1:概念:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子节点都在同一层上;
2:特点:一棵深度为k,且有 2^k−1 个节点称之为满二叉树.
4: 完全二叉树概念和特点
1:在层序遍历中,只要遇到一个空节点,若后面全是空节点,则为完全二叉树,若空节点之后还有结点,则不是完全二叉树;
2:具有n结点的完全二叉树的深度k=log2(n+1)向上取整.
5:平衡二叉树
平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二:面试题
1:题目
1:二叉树遍历(创建树)
2:二叉树的下一个节点
3:树的子结构
4:二叉树的镜像
5:对称的二叉树
6:从上到下打印二叉树
7:二叉搜索树的后序遍历序列
8:二叉树中和为某一值的路径
9:二叉搜索树和双向链表
10:序列化二叉树
11:二叉搜索树的第k大节点
12:二叉树的深度
13:重建二叉树(前序和中序,后序和中序)
14:平衡二叉树
15:Recover Binary Search Tree 复原二叉搜索树
16:判断相同树(Same Tree)
17:Validate Binary Search Tree 验证二叉搜索树
18:Unique Binary Search Trees II (唯一二叉排序树的个数)
19:Binary Tree Maximum Path Sum 求二叉树的最大路径和(和8相似)
20:二叉树最小深度
21: 二叉树-----后序遍历
22: Binary Tree Inorder Traversal (二叉树的中序遍历)
23:把二叉树打印成多行(和6题相似)
24:按之字打印二叉树
25:最近公共祖先
2:代码部分(代码全部是在牛客网oj环境下编译通过的)
:一:二叉树遍历(创建树)
1:题目描述:
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
2:代码
#include <iostream>
#include <string>
using namespace std;
string str;
int i;
struct TreeNode
{
char val;
struct TreeNode *lchild, *rchild;
TreeNode(char c) :val(c), lchild(NULL), rchild(NULL) {}
};
TreeNode* createTree() {
char c = str[i++];
if (c == '#') return NULL;
TreeNode *root = new TreeNode(c);
root->lchild = createTree();
root->rchild = createTree();
return root;
}
void inOrderTraversal(TreeNode* root) {
if (!root) return;
inOrderTraversal(root->lchild);
cout << root->val << " ";
inOrderTraversal(root->rchild);
}
int main() {
while (cin >> str) {
i = 0;
TreeNode *root = createTree();
inOrderTraversal(root);
cout << endl;
}
return 0;
}
二:二叉树的下一个节点
1:题目描述:
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
2:代码和思路
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode==NULL)
{
return NULL;
}
//接下来有两种情况:
//1:如果一个结点有右子树,那么它的下一个结点就是她的右子树的最左子结点
//2:如果一个结点没有右子树,分为两种情况
// 1:如果结点是它父节点左子树时,那么它的下一个结点就是它的父节点
// 2:如果节点是它父节点的右子树时,那么它的下一个节点是就是它的父节点的父节点的父节点,
//直到其中一个父节点是它父节点的左子结点的结点时
//pNode->next是pNode结点的父节点
if(pNode->right!=NULL)
{
pNode=pNode->right;
while(pNode->left!=NULL)
{
pNode=pNode->left;
}
return pNode;
}
while(pNode->next!=NULL)
{
TreeLinkNode* pParent=pNode->next;
if(pParent->left==pNode)
{
return pParent;
}
pNode=pNode->next;
}
return NULL;
}
};
三:树的子结构
1:题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
2:代码和思路
Step1.在树A中找到和B的根结点的值一样的结点R;
Step2.判断树A中以R为根结点的子树是不是包含和树B一样的结构。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result = false;
if(pRoot1 !=NULL && pRoot2 !=NULL)
{
if(pRoot1->val == pRoot2->val) //如果根结点配对了,则直接继续
{
result = HasSubTree2(pRoot1,pRoot2);
}
if(!result)
{
result = HasSubtree(pRoot1->left,pRoot2);
}
if(!result)
{
result = HasSubtree(pRoot1->right,pRoot2);
}
}
return result;
}
bool HasSubTree2(TreeNode* pRoot1,TreeNode* pRoot2)
{
if(pRoot2==NULL)
return true;
if(pRoot1 == NULL)
return false;
if(pRoot1->val != pRoot2->val)
return false;
return HasSubTree2(pRoot1->left,pRoot2->left)
&& HasSubTree2(pRoot1->right,pRoot2->right);
}
};
四:二叉树的镜像
1:题目描述:
2:代码和思路
思路: 1:左子树取镜像右子树
2:左右子树交换
如下图:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot==NULL)
{
return ;
}
if(pRoot->left==NULL&&pRoot->right==NULL)
{
return ;
}
TreeNode *pLeft=pRoot->left;
pRoot->left=pRoot->right;
pRoot->right=pLeft;
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
五:对称的二叉树
1:题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
2:代码和思路
思路:首先根节点以及其左右子树,左子树的左子树和右子树的右子树相同
* 左子树的右子树和右子树的左子树相同即可,采用递归
* 非递归也可,采用栈或队列存取各级子树根节点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
return isSymmetrical(pRoot,pRoot);
}
bool isSymmetrical(TreeNode* pRoot1,TreeNode* pRoot2)
{
if(pRoot1==NULL&&pRoot2==NULL)
{
return true;
}
if(pRoot1==NULL||pRoot2==NULL)
{
return false;
}
if(pRoot1->val!=pRoot2->val)
{
return false;
}
return (isSymmetrical(pRoot1->left,pRoot2->right)&&
isSymmetrical(pRoot1->right,pRoot2->left));
}
};
六:从上到下打印二叉树
1:题目描述:
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
2:代码和思路
思路:可以利用队列的先进先出特性,先将二叉树的根节点存进队列,并依次从左到右存进树的左右节点,最后用一个动态数组存储数据就可以了;
1:每次打印一个节点的时候,如果该节点有子节点,则把该节点的子节点放到一个队列的末尾;
2:接下来到队列的头部取出最早进入队列的节点,重复前面的打印工作,直至队列中所有的节点都被打印出来
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> result;
if(NULL == root)
return result;
queue<TreeNode* > q;
q.push(root);
while(!q.empty())
{
TreeNode* temp = q.front();
q.pop();
result.push_back(temp->val);
if(NULL != temp->left)
q.push(temp->left);
if(NULL != temp->right)
q.push(temp->right);
}
return result;
}
};
下面是用双端队列写的
vector<int> res;
if(root==NULL) //需要判断是否为空
return res;
deque<TreeNode*> dequeTreeNode;
dequeTreeNode.push_back(root);
while(dequeTreeNode.size()) //循环终止条件:队列元素全部打印为空
{
TreeNode* pNode= dequeTreeNode.front(); //返回双端队列dequeTreeNode的首元素给pNode
dequeTreeNode.pop_front(); //删除双端队列dequeTreeNode的首元素
res.push_back(pNode->val); //将此时的dequeTreeNode第一个元素存入到输出容器res中
if(pNode->left) //若当前结点的左结点不为空,将其左结点存入到双端队列dequeTreeNode中
dequeTreeNode.push_back(pNode->left);
if(pNode->right)
dequeTreeNode.push_back(pNode->right); 若当前结点的右结点不为空,将其右结点存入到双端队列dequeTreeNode中
}
return res;
七:二叉搜索树的后序遍历序列
1:题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
2:代码和思路
思路:
已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。
1、确定root;
2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
3、遍历右子树,若发现有小于root的值,则直接返回false;
4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
vector<int> leftTree,rightTree;
int root; // 根结点
if(sequence.empty()) return false;
int index = 0; // 标记左右子树界限
int len = sequence.size();
root = sequence[len-1];
int i=0;
for(;i<len-1;++i)
{
if(sequence[i]>root) break; // 找到第一个大于根结点的位置,则左边为左子树,右边为右子树
}
for(int j=i;j<len-1;++j) // 循环时去除root,因此为len-1
{
if(sequence[j]<root) return false; // 有一个小于root,则返回false
}
if(i!=0)
{
// 即有左子树
for(int m=0;m<i;++m)
{
leftTree.push_back(sequence[m]);
}
}
if(i!=len-2)
{
for(int j=i;j<len-1;++j)
{
rightTree.push_back(sequence[j]);
}
}
bool left = true,right = true; // 看左右子树是否是二叉搜索树
if(leftTree.size()>1) VerifySquenceOfBST(leftTree);
if(rightTree.size()>1) VerifySquenceOfBST(rightTree);
return (left&&right);
}
};
八:二叉树中和为某一值的路径
1:题目描述:
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
2:代码和思路
思路: 如下图所示的例子,给定了二叉树与整数22,则可以打印两条路径。第一条10,5,7、第二条10,12
1:题目中定义路径:从树的根节点开始一直到叶子结点所经过的结点形成一条路径
也就是说每条满足条件的路径都是以根节点开始,叶子结点结束,如果想得到所有根节点到叶子结点的路径(不一一定满足和为某整数的条件),需要遍历整棵树,还要先遍历根节点,所以采用先序遍历。
2:10-->5-->4 已近到达叶子结点,不满足要求22,因此该路径访问结束,需要访问下一个路径
因为在访问节点的过程中,我们并不知道该路径是否满足要求,所以我们每访问一个节点就要记录该结点
3:访问下有一个结点前,要先从结点4退回到结点5,再访问下一个结点7,因为4不在去往7的路径上,所以要在路径中将4删除
4:10-->5-->7 满足要求,保存该路径
5:访问下一个结点,从结点7回到结点5再回到结点10
6:10-->12,满足要求,保存该路径
整个流程如下图所示:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void findAllPath
(TreeNode* root,int expectNumber,int currentSum,vector<vector<int> >& allpath,vector<int>& path){
//记录当前访问的结点
path.push_back(root->val);
//记录访问到当前节点 所有结点的和
currentSum+=root->val;
//判断是否为叶子结点 true-是
bool isLeaf=root->left==NULL && root->right==NULL;
//如果为叶子结点, 判断路径的和是否满足要求,满足,保存该路径
if(currentSum==expectNumber && isLeaf)
allpath.push_back(path);
if(root->left!=NULL)
findAllPath(root->left,expectNumber,currentSum,allpath,path);
if(root->right!=NULL)
findAllPath(root->right,expectNumber,currentSum,allpath,path);
//如果是叶子结点又不满足要求,退回到父结点,删除当前节点
path.pop_back();
}
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
vector<vector<int> > allpath;
vector<int> path;
//边界条件
if(root==NULL)
return allpath;
int currentSum=0;
findAllPath(root,expectNumber,currentSum,allpath,path);
return allpath;
}
};
九:二叉搜索树和双向链表
1:题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
2:代码和思路
思路:
1:首先我们必须清楚二叉搜索树的概念,然后我们需要知道用哪种遍历方式---中序遍历二叉搜索树是按照从小到大的顺序排列的
2:递归方式,因为每次递归返回的是链表的头节点,而添加新元素是在链表的尾部,而当前递归返回的又是链表的头节点,因此需要不断左右遍历链表
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode* pLastNodeSlist=NULL;
CovertNode(pRootOfTree,&pLastNodeSlist);
//需要返回头结点
TreeNode* pHeadOfList=pLastNodeSlist;
while(pHeadOfList!=NULL&&pHeadOfList->left!=NULL)
{
pHeadOfList=pHeadOfList->left;
}
return pHeadOfList;
}
void CovertNode(TreeNode* pNode, TreeNode** pLastNodeSlist)
{
//判空
if( pNode==NULL)
{
return ;
}
TreeNode* pCurrent=pNode;
//遍历左子树
if(pCurrent->left!=NULL)
{
CovertNode(pCurrent->left,pLastNodeSlist);
}
//根节点的衔接
pCurrent->left=*pLastNodeSlist;
if(*pLastNodeSlist!=NULL)
{
(*pLastNodeSlist)->right=pCurrent;
}
*pLastNodeSlist=pCurrent;
//右子树的遍历
if(pCurrent->right!=NULL)
{
CovertNode(pCurrent->right,pLastNodeSlist);
}
}
};
十:序列化二叉树
1:题目描述:
请实现两个函数,分别用来序列化和反序列化二叉树
2:代码和思路
思路:
在这里我们首先搞懂序列化和反序列化的概念
• 序列化:对于上图中的树,进行前序遍历时,先访问到1,然后2,然后4,4的左右子结点都为空,可以用一个特殊字符替代,譬如用$,所以上图中的二叉树前序遍历表示就是“1,2,4,$,$,$,3,5,$,$,6,$,$"。
• 反序列化:重建的时候,访问的第一个结点为根结点,接下来的数字是2,它是根结点的左子结点。接下来的是4,它是2的左子结点。然后遇到两个$,说明4的左右子结点都是NULL。接下来结点回退,访问4的父结点2,又是$,说明2的右子结点是NULL。再返回到根结点,这时候该建立它的右子结点了,下一个数值是3,说明3是根结点的右子结点,剩下的步骤和左子树部分类似。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
TreeNode* decode(char *&str) {
if(*str=='#'){
str++;
return NULL;
}
int num = 0;
while(*str != ',')
num = num*10 + (*(str++)-'0');
str++;
TreeNode *root = new TreeNode(num);
root->left = decode(str);
root->right = decode(str);
return root;
}
public:
char* Serialize(TreeNode *root) {
if(!root) return "#";
string r = to_string(root->val);
r.push_back(',');
char *left = Serialize(root->left);
char *right = Serialize(root->right);
char *ret = new char[strlen(left) + strlen(right) + r.size()];
strcpy(ret, r.c_str());
strcat(ret, left);
strcat(ret, right);
return ret;
}
TreeNode* Deserialize(char *str) {
return decode(str);
}
};
十一:二叉搜索树的第k大节点
1:题目描述:
给定一棵二叉搜索树,请找出其中的第k大的结点。例如, 层序:(5,3,7,2,4,6,8) 中,按结点数值大小顺序第三大结点的值为4。
2:代码和思路:
思路:对于这道题,我们同样必须知道二叉搜索树的概念和特点,我们还需要知道要采用哪种遍历方式--------根据二叉搜索树的特点,我们采用中序遍历的方式,得到的是从小到大的节点顺序
十二:二叉树的深度
1:题目描述:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度
2:代码和思路:
思路:
a:如果一棵树只有一个节点,那么它的深度为1;
b:如果根节点只有左子树没有右子树(或者只有右子树没有左子树),那么它的深度为其左子树(右子树)的深度加1;
c:如果既有左子树又有右子树,那么它的深度就是其左或右子树的深度的较大值加1.
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==NULL)
{
return 0;
}
if(pRoot->left==NULL&&pRoot->right==NULL)
{
return 1;
}
int left=TreeDepth(pRoot->left);
int right=TreeDepth(pRoot->right);
return (left>right)?left+1:right+1;
}
};
十三:重建二叉树
1:题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
2:代码和思路:
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
//输入合法性判断,不能为空,长度大于0;
if(pre.empty()||vin.empty()){
return NULL;
}
int rootValue=pre[0];//根节点为前序的第一个数;
// int index=vin.start();//定义一个下标,指向中序的开始位置;
TreeNode* root=new TreeNode(rootValue);//创建当前根节点,并为根节点赋值
int leftlen=0;
int rightlen=0;
//在中序序列中找到根节点的位置(必存在)
for (int i = 0; i < vin.size(); i++){
if (vin[i] == rootValue){
leftlen= i;//左子树的长度为i;
rightlen = vin.size() - i - 1;//右子树的长度
break;
}
}
//中序的左边为左子树中序序列,右边为右子树中序序列;
//前序的[startpre+1,leftlen]为左子树,[leftlen+1,endpre]为右子树;
vector<int> leftPre (pre.begin() + 1, pre.begin() +1 + leftlen);
vector<int> leftIn (vin.begin(), vin.begin() + leftlen);
vector<int> rightPre (pre.begin() + leftlen + 1, pre.begin() + leftlen + 1 + rightlen);
vector<int> rightIn (vin.begin() + leftlen + 1, vin.begin() + leftlen + 1 + rightlen);
//递归构建左右子树
root->left = reConstructBinaryTree(leftPre, leftIn);
root->right = reConstructBinaryTree(rightPre, rightIn);
return root;
}
};
下面是用后序和中序来重建二叉树
思路:构造该二叉树的过程如下:
1. 根据后序序列的最后一个元素建立根结点;
2. 在中序序列中找到该元素,确定根结点的左右子树的中序序列;
3. 在后序序列中确定左右子树的后序序列;
4. 由左子树的后序序列和中序序列建立左子树;
5. 由右子树的后序序列和中序序列建立右子树
代码
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode *buildTree(vector<int> &inorder, vector<int> &postorder) {
if(inorder.empty()||postorder.empty()){
return NULL;
}
int rootValue=postorder[postorder.size()-1];
int leftlen=0;
int rightlen=0;
TreeNode* root=new TreeNode(rootValue);
for(int i=0;i<inorder.size();i++){
if(inorder[i]==rootValue){
leftlen=i;
rightlen=inorder.size()-i-1;
break;
}
}
vector<int>leftIn(inorder.begin(),inorder.begin()+leftlen);
vector<int>rightIn(inorder.begin()+leftlen+1,inorder.begin()+leftlen+1+rightlen);
vector<int>leftPost(postorder.begin(),postorder.begin()+leftlen);
vector<int>rightPost(postorder.begin()+leftlen,postorder.begin()+leftlen+rightlen);
root->left=buildTree(leftIn,leftPost);
root->right=buildTree(rightIn,rightPost);
return root;
}
};
十四:平衡二叉树
1:题目描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
2:思路
我们针对这道题,首先必须清楚平衡二叉树的概念和特点:
如下是一棵平衡二叉树
3:代码
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
int depth=0;
return IsBalanced(pRoot,&depth);
}
bool IsBalanced(TreeNode* pRoot,int *pDepth)
{
//true表示平衡
//false表示不平衡
if(pRoot==NULL)
{
*pDepth=0;
return true;
}
int left,right;
if(IsBalanced(pRoot->left,&left)&&IsBalanced(pRoot->right,&right))//用递归求高度
{
if(left-right>=-1&&left-right<=1)//任意节点左右子树高度不能超过1
{
*pDepth=1+(left>right?left:right);//求得二叉树高度
return true;
}
}
return false;
}
};
十五:Recover Binary Search Tree 复原二叉搜索树
1:题目描述:
Two elements of a binary search tree (BST) are swapped by mistake.
Recover the tree without changing its structure.
Note:
A solution using O(n) space is pretty straight forward. Could you devise a constant space solution?
confused what "{1,#,2,3}" means? > read more on how binary tree is serialized on OJ.
2:思路和代码:
二叉搜索树的概念:二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。现在我们来用下面的树分析:
正确的中序遍历为:左根右的原则:[1234567]
现在的中序遍历为:[1254367]
很明显3和5颠倒了。那么在中序遍历时:当碰到第一个逆序时:为5->4,那么将first指向5,second指向4,注意,此时first已经确定下来了。然后pre和root一直向后遍历,直到碰到第二个逆序时:4->3,此时将second指向3,那么first和second都已经确定,只需要交换节点的值即可。prev指针用来比较中序遍历中相邻两个值的大小关系,很巧妙。
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*//*
4
/ \
2 6
/ \ / \
1 5 3 7
正确的中序遍历为:左根右的原则:[1234567]
现在的中序遍历为:[1254367]
很明显3和5颠倒了。那么在中序遍历时:当碰到第一个逆序时:为5->4,那么将first指向5,second指向4,
注意,此时first已经确定下来了。然后parent和root一直向后遍历,直到碰到第二个逆序时:4->3,此时将second指向3,
那么first和second都已经确定,只需要交换节点的值即可。pre指针用来比较中序遍历中相邻两个值的大小关系*/
class Solution {
public:
//非递归
/*void recoverTree(TreeNode *root) {
TreeNode *first = NULL, *second = NULL, *parent = NULL;
TreeNode *cur, *pre;
cur = root;
while (cur) {
if (cur->left==NULL) {
if (parent && parent->val > cur->val) {
if (!first) first = parent;
second = cur;
}
parent = cur;
cur = cur->right;
} else {
pre = cur->left;
while (pre->right && pre->right != cur) pre = pre->right;
if (!pre->right) {
pre->right = cur;
cur = cur->left;
} else {
pre->right = NULL;
if (parent->val > cur->val) {
if (!first) first = parent;
second = cur;
}
parent = cur;
cur = cur->right;
}
}
}
if (first && second) swap(first->val, second->val);*/
//递归版本的
//定义三个指针,*pre用来比较大小,*first用来存放第一个错位的数,*second用来存放第二个错位的数
TreeNode *pre;
TreeNode *first;
TreeNode *second;
void recoverTree(TreeNode *root) {
pre = NULL;
first = NULL;
second = NULL;
inorder(root);
if (first !=NULL&& second!=NULL) //交换两个错位的值
swap(first->val, second->val);
}
void inorder(TreeNode *root) {
if (root==NULL)
return;
inorder(root->left);//递归遍历左子树
if (pre==NULL)
pre = root;
else {
if (pre->val > root->val) {
if (first==NULL)
first = pre;
second = root;
}
pre = root;
}
inorder(root->right);//递归遍历右子树
}
};
十六:判断相同树(Same Tree)
1:题目描述:
Given two binary trees, write a function to check if they are equal or not.
Two binary trees are considered equal if they are structurally identical and the nodes have the same value.
2:思路:这个和对称的二叉树思路相似。
我们主要看两棵树是否相同,首先它们的高度必须相同,然后它们对应的结点的值相同。不管是递归还是非递归,它们都是一样的,每一个结点都对应相同的值,我们采用前序遍历的方法,根左右,首先都传进去左子树,进行递归,然后再是右子树(递归),非递归也是相同的思路。
3:代码
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSameTree(TreeNode *p, TreeNode *q) {
//非递归版本
stack<TreeNode*> s1, s2;
if (p!=NULL)
s1.push(p);//入栈
if (q!=NULL)
s2.push(q);
while (s1.empty()==NULL &&s2.empty()==NULL) {
TreeNode *t1 = s1.top(); //栈顶元素
s1.pop();//出栈
TreeNode *t2 = s2.top();
s2.pop();
if (t1->val != t2->val)
return false;
if (t1->left!=NULL)
s1.push(t1->left);
if (t2->left!=NULL)
s2.push(t2->left);
if (s1.size() != s2.size())
return false;
if (t1->right!=NULL)
s1.push(t1->right);
if (t2->right!=NULL)
s2.push(t2->right);
if (s1.size() != s2.size())
return false;
}
return s1.size() == s2.size();
// 递归版本
/* if(p==NULL&&q==NULL)//都为空,返回正确
{
return true;
}
if(p==NULL||q==NULL)
// if((p==NULL&&q!=NULL)||(p!=NULL&&q==NULL))
{
return false;
}
if(p->val!=q->val)
{
return false;
}
return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);*/
}
};
十七:Validate Binary Search Tree 验证二叉搜索树
1:题目描述:
Given a binary tree, determine if it is a valid binary search tree (BST).
Assume a BST is defined as follows:
The left subtree of a node contains only nodes with keys less than the node's key.
The right subtree of a node contains only nodes with keys greater than the node's key.
Both the left and right subtrees must also be binary search trees。
2:思路
二叉搜索树定义:
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
Example :
这里用中序遍历结果为:【1,3,4,6,7,8,10,13,14】,由此可见,用中序遍历搜索二叉树,遍历结果是从小到大排序的。
反例:
10
/ \
4 15
/ \ / \
2 5 6 17
由上图可得:15,6,17局部满足BST树,但是6<10,所以不是BST树,其中序遍历结果为:【2,4,5,10,6,15,17】,中序遍历结果不是从小到大排序的。
思路:我们可以根据其中序遍历的特点来进行判断是否合法。
3:代码
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode *root) {
//方法一:采用中序递归遍历,求得遍历结果,放入容器vals中,然后比较大小
if (root==NULL)
return true;
vector<int> vals;
inorder(root, vals);
for (int i = 0; i < vals.size() - 1; ++i) {
if (vals[i] >= vals[i + 1])//中序从小到大
return false;
}
return true;
}
void inorder(TreeNode *root, vector<int> &vals) {//中序遍历
if (root==NULL) return;
inorder(root->left, vals);
vals.push_back(root->val);
inorder(root->right, vals);
//方法二:采用中序根左右的递归遍历,同时判断是否合法,中序遍历结果从小到大排序
/* TreeNode* prev = NULL;
return is_bst(root, prev);
}
bool is_bst(TreeNode* root, TreeNode*& prev){
if(root == NULL)
return true;
if(is_bst(root->left, prev)==NULL)
return false;
if(prev != NULL && prev->val >= root->val)
return false;
prev = root;
return is_bst(root->right, prev); */
}
};
十八:Unique Binary Search Trees II (唯一二叉排序树的个数)
1:题目描述:
输出用1–n这几个数字能组成的所有BST.)
Given n, generate all structurally unique BST's (binary search trees) that store values 1...n.
For example,
Given n = 3, your program should return all 5 unique BST's shown below.
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
confused what"{1,#,2,3}"means? > read more on how binary tree is serialized on OJ.
OJ's Binary Tree Serialization:The serialization of a binary tree follows a level order traversal, where '#' signifies a path terminator where no node exists below.
Here's an example:
1
/ \
2 3
/
4
\
5
The above binary tree is serialized as"{1,2,3,#,#,4,#,#,5}".
2:思路::
BST(二叉排序树):中序遍历的结果为非递减序列,并且节点(个数和值)相同的不同二叉树的中序遍历结果都相同;
当左子树的节点个数确定后,右子树的个数也随之确定;
当节点个数为0或1时,二叉树只有1种,表示为f(0)=1,f(1)=f(0)*f(0);
当节点个数为2时,总的种类数=左子树为空f(0)*右子树不为空f(1)+左子树不为空f(1)*右子树为空f(0),即f(0)*f(1)+f(1)*f(0)=2种;
当节点个数为3时,有左子树为空f(0)*右子树不为空f(2)+左子树不为空f(2)*右子树为空f(0)+左右子树均不为空f(1)*f(1),即f(0)*f(2)+f(2)*f(0)+f(1)*f(1)=1*2+2*1+1*1=5种;
……
当节点个数为n时,结果为f(0)*f(n-1)+f(1)*f(n-2)+……+f(n-2)*f(1)+f(n-1)*f(0);
3:代码:
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<TreeNode *> generateTrees(int n) {
if (n <= 0)
return helper(1, 0);//一个节点就能创建一棵树,为了减少递归
return helper(1,n);//将结点个数传进去
}
private:
vector<TreeNode*> helper(int start,int end)
{
vector<TreeNode*> subTree;
if(start>end)//判断
{
subTree.push_back(NULL);
return subTree;
}
for (int k = start; k <= end; k++)
{
// 假设k为根节点,根节点左边是左子树,右边是右子树,一分为二,依次往下递归
//返回不同二叉树的根节点,有几个就返回几个根节点,然后装进容器里面
vector<TreeNode*> leftSubs = helper(start, k - 1);
vector<TreeNode*> rightSubs = helper(k + 1, end);
//左子右子树和根节点结合
//以k为根节点的树的个数,等于左子树的个数乘以右子树的个数
for(int i=0;i<leftSubs.size();i++)//for (auto i : leftSubs)
{
for(int j=0;j<rightSubs.size();j++)// for (auto j : rightSubs)
{
TreeNode *node = new TreeNode(k);//为每个树申请新空间
node->left =leftSubs[i];//左子树
node->right = rightSubs[j];//右子树
subTree.push_back(node);//根节点
}
}
}
return subTree;//返回树
}
};
十九:Binary Tree Maximum Path Sum 求二叉树的最大路径和(和8相似)
1:题目要求:
Given a binary tree, find the maximum path sum.
The path may start and end at any node in the tree.
For example:
Given the below binary tree,
1
/ \
2 3
Return 6.
2:思路:我们先来看一个例子,这个树的最远路径和为7+11+4+13
4
/ \
11 13
/ \
7 2
树的递归解法一般都是递归到叶节点,然后开始边处理边回溯到根节点。
那么我们就假设此时已经递归到结点7了,那么其没有左右子节点,所以如果以结点7为根结点的子树最大路径和就是7。
然后回溯到结点11,如果以结点11为根结点的子树,我们知道最大路径和为7+11+2=20。
但是当回溯到结点4的时候,对于结点11来说,就不能同时取两条路径了,只能取左路径,或者是右路径,所以当根结点是4的时候,那么结点11只能取其左子结点7,因为7大于2。
所以,对于每个结点来说,我们要知道经过其左子结点的path之和大还是经过右子节点的path之和大。那么我们的递归函数返回值就可以定义为以当前结点为根结点,到叶节点的最大路径之和,然后全局路径最大值放在参数中,用结果res来表示。
在递归函数中,如果当前结点不存在,那么直接返回0。
否则就分别对其左右子节点调用递归函数,由于路径和有可能为负数,而我们当然不希望加上负的路径和,所以我们和0相比,取较大的那个,就是要么不加,加就要加正数。
然后我们来更新全局最大值结果res,就是以左子结点为终点的最大path之和加上以右子结点为终点的最大path之和,还要加上当前结点值,这样就组成了一个条完整的路径。
而我们返回值是取left和right中的较大值加上当前结点值,因为我们返回值的定义是以当前结点为终点的path之和,所以只能取left和right中较大的那个值,而不是两个都要。
总体思路:
0:我们先从根节点开始,开始左子树,右子树的遍历;
1:我们先遍历左子树,然后每次求取它的路径最大值;
2:我们接着遍历右子树,然后每次求取它的路径最大值;
3:每次我们都会 更新sum,求得每次的最大值;
4:递归函数中,我们每次求得左和右中较大的那个作为返回值,然后进行每次的递归;
关于 sum=INT_MIN;//考虑到负数的情况,从最小的整数开始比较
3:代码
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
int sum;
public:
int maxPathSum(TreeNode *root) {
sum=INT_MIN;//考虑到负数的情况,从最小的整数开始比较
help(root);//从根节点开始递归
return sum;//返回路径的最大值
}
int help(TreeNode* root){
if(!root) return 0;//递归到叶节点的子节点,返回0
int left = max(0, help(root->left));//对根节点的左叶节点开始递归
int right = max(0, help(root->right));//对根节点的右叶节点开始递归
sum = max(sum, left+right+root->val);//更新现有路径最大值
return max(left, right)+root->val;
}
};
二十:二叉树最小深度
1:题目描述
Given a binary tree, find its minimum depth.The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.
给定一个二叉树,求出它的最小深度,最小深度是从根节点到最近的叶子节点的最短路径的节点数。
2:思路和代码:
以下图为例:1:我们首先判断是否是空树的情况,很显然这个不是,根节点不为空;
2:然后我们判断是不是左右子树都没有;
3:然后我们判断没有左子树,有右子树的情况,然后递归,以此时的右子树为根节点,从上到下继续1,2,3操作;
4:然后我们判断没有右子树,有左子树的情况,然后递归,以此时的左子树为根节点,从上到下继续1,2,3,4操作;
5:最后左右子树都有,我们将左右子树看成新的树,继续1,2,3,4,5的操作;
class Solution {
public:
int run(TreeNode *root) {
if(root==NULL)//根节点为空,即树为空
return 0;
if((root->left==NULL)&&(root->right==NULL))//没有左右树
return 1;
if(root->left==NULL)//没有左子树,以右子树为根,看它的左右子树
{
return run(root->right)+1; //子问题,继续递归
}else if(root->right==NULL)//同上
{
return run(root->left)+1;
}else {
return (run(root->right)<run(root->left)?run(root->right)+1:run(root->left)+1);
}
}
};
二十一: 二叉树-----后序遍历
1:题目描述
将二叉树用后序遍历写出来
2:代码和思路
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
vector<int> s;
public:
vector<int> postorderTraversal(TreeNode *root) {
if(root==NULL)
{
return s;
}
postorderTraversal(root->left);
postorderTraversal(root->right);
s.push_back(root->val);
return s;
}
};
二十二: Binary Tree Inorder Traversal (二叉树的中序遍历)
1:题目描述:
Given a binary tree, return the inorder traversal of its nodes' values.
For example:
Given binary tree {1,#,2,3},
1
\
2
/
3
return [1,3,2].
Note: Recursive solution is trivial, could you do it iteratively?
confused what "{1,#,2,3}" means? > read more on how binary tree is serialized on OJ.
2:思路和代码:
根据中序遍历原则:左根右的原则,运用递归来遍历,当然也可以写非递归版本,需要用到栈
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
vector<int> s;//定义一个容器
public:
vector<int> inorderTraversal(TreeNode *root) {
if(root==NULL)
{
return s;
}
inorderTraversal(root->left);
s.push_back(root->val);
inorderTraversal(root->right);
return s;
}
};
二十三:把二叉树打印成多行(和6题相似)
1:题目描述:
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
2:思路
* 按层次输出二叉树
* 访问根节点,并将根节点入队。
* 当队列不空的时候,重复以下操作。
* 1、弹出一个元素。作为当前的根节点。
* 2、如果根节点有左孩子,访问左孩子,并将左孩子入队。
* 3、如果根节点有右孩子,访问右孩子,并将右孩子入队。
3:代码
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > vec;
if(pRoot == NULL) return vec;
queue<TreeNode*> q;//队列
q.push(pRoot);
while(!q.empty())
{
int lo = 0, hi = q.size();
vector<int> c;
while(lo++ < hi)
{
TreeNode *t = q.front();
q.pop();//出队
c.push_back(t->val);
//如果有左孩子,右孩子,就让左右孩子入队
if(t->left) q.push(t->left);
if(t->right) q.push(t->right);
}
vec.push_back(c);
}
return vec;
}
};
二十四:按之字打印二叉树
1:题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
2:思路
在这里我用两个栈来实现
如图:
3:代码:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > result;
if( pRoot != NULL)
{
stack<TreeNode*> stack1, stack2;
stack1.push( pRoot);
while(!stack1.empty() || !stack2.empty() )
{
vector<int> ret1,ret2;
TreeNode* cur = NULL;
while( !stack1.empty())
{
//偶数行放栈2
cur = stack1.top();
if( cur->left)
stack2.push(cur->left);
if(cur->right)
stack2.push(cur->right);
ret1.push_back(stack1.top()->val); //保存奇数行数据
stack1.pop();
}
if(ret1.size())
result.push_back(ret1);
while( !stack2.empty())
{
//奇数行放栈1
cur = stack2.top();
if(cur->right)
stack1.push( cur->right);
if(cur->left)
stack1.push( cur->left);
ret2.push_back(stack2.top()->val); //保存偶数行数据
stack2.pop();
}
if(ret2.size())
result.push_back(ret2);
}
}
return result;
}
};
二十五:最近公共祖先
1:题目描述:
有一棵无穷大的满二叉树,其结点按根结点一层一层地从左往右依次编号,根结点编号为1。现在有两个结点a,b。请设计一个算法,求出a和b点的最近公共祖先的编号。
给定两个int a,b。为给定结点的编号。请返回a和b的最近公共祖先的编号。注意这里结点本身也可认为是其祖先。
测试样例:
2,3
返回:1
2:思路和代码
class LCA {
public:
int getLCA(int a, int b) {
// write code here
//思路:满二叉树的子节点与父节点之间的关系为root = child / 2
//利用这个关系,如果a != b,就让其中的较大数除以2, 如此循环知道a == b,
//即是原来两个数的最近公共祖先
/* while(a != b)
{
if(a > b)
a /= 2;
else
b /= 2;
}
return a;*/
if (a == b)
return a;
return a>b? getLCA(a/2, b): getLCA(a, b/2);
}
};
如果有想要文档的,留言私聊我,我分享给大家我的文档。