剑指Offer——二叉树部分
一、二叉树的构建相关
1.1、第4题、重建二叉树
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
基本思路:
对于一个二叉树而言,我们关注遍历过程中根节点的位置,对于前序遍历而言,根节点是第一个被遍历的,也就是说,我们可以先通过前序遍历来确定根节点的位置。然后,根据中序遍历的特点,我们可以通过根节点来将中序遍历的集合分成左子树和右子树的两个部分。然后递归构建二叉树即可。
代码实现:
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
if(pre.size() == 0 || vin.size() ==0)
return NULL;
TreeNode * root = createTree(pre,vin);
return root;
}
TreeNode * createTree(vector<int> pre,vector<int> vin)
{
if(pre.size() == 0)
return NULL;
int root = pre[0];
int i=0;
while(vin[i] != root)
i++;
int j = 0;
vector<int> pre_left,pre_right,vin_left,vin_right;
for(;j<i;j++)
{
vin_left.push_back(vin[j]);
pre_left.push_back(pre[j+1]);
}
j += 1;
for(;j<vin.size();j++)
{
vin_right.push_back(vin[j]);
pre_right.push_back(pre[j]);
}
TreeNode * node = new TreeNode(root);
node->left = createTree(pre_left,vin_left);
node->right = createTree(pre_right,vin_right);
return node;
}
};
1.2、第61题 序列化二叉树
题目描述:
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
解题思路:
首先,我们考虑如何将一棵二叉树进行序列化,我们的目标是将二叉树序列成一个字符串,核心思想是通过前序遍历的方式来将二叉树进行序列化。当遍历二叉树的时候,如果碰到NULL指针,则将其标记为#。为了分开不同的节点,我们采用".“来分割出不同的节点表示。确定序列化的方式之后,反序列化就比较简单了,只需要按照”.“来分开字符串中的数据,遇到”#"设置节点为空即可:
代码实现:
class Solution {
public:
string Shelp(TreeNode *root)
{
if(!root)
return "#";
return to_string(root->val)+"."+Shelp(root->left)+"."+Shelp(root->right);
}
char* Serialize(TreeNode *root) {
string res = Shelp(root);
char *sre = new char[res.size()+1];
strcpy(sre,const_cast<char*>(res.c_str()));
return sre;
}
TreeNode * dHelp(stringstream &ss)
{
string str;
getline(ss,str,'.');
if(str=="#")
return NULL;
else{
TreeNode * node = new TreeNode(stoi(str));
node->left = dHelp(ss);
node->right = dHelp(ss);
return node;
}
}
TreeNode* Deserialize(char *str) {
stringstream ss(str);
return dHelp(ss);
}
};
2、二叉树遍历相关
2.1 第22题,从上往下打印二叉树
题目描述:
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
基本思路:
这道题的主要考察的就是树的层次遍历,其基本思想是使用队列来保存节点。每次从栈中获取一个节点,输出结果,同时将其左子节点和右子节点入栈。
代码实现
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> res;
if(!root)
return res;
queue<TreeNode *> qu;
qu.push(root);
while(!qu.empty())
{
TreeNode * node = qu.front();
qu.pop();
res.push_back(node->val);
if(node->left)
qu.push(node->left);
if(node->right)
qu.push(node->right);
}
return res;
}
};
2.2、第59题,将二叉树打印成多行
题目描述:
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
基本思路:
该题的思路也是一个层次遍历的过程,与上一道题的唯一区别在于我们需要确定每一层的节点的数量,按照该层的数量,按行读取,这个思路的实现很简单,只需要在遍历的时候按照当前队列中的元素的个数来确定当层的数量。
代码实现:
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if(pRoot == NULL)
return res;
queue<TreeNode *> qu;
qu.push(pRoot);
while(!qu.empty())
{
int size = qu.size();
vector<int> temp;
for(int i=0;i<size;i++)
{
TreeNode * node = qu.front();
qu.pop();
temp.push_back(node->val);
if(node->left)
qu.push(node->left);
if(node->right)
qu.push(node->right);
}
res.push_back(temp);
}
return res;
}
};
2.3 第58题 按之字形打印二叉树
题目描述:
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
基本思路:
相比于上一道题,我们只需要判断一下当前层是奇数层还是偶数层,如果是偶数层,将该层的结果反向输出结果即可。
代码实现:
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if(!pRoot)
return res;
int high = 0;
queue<TreeNode *> qu;
qu.push(pRoot);
while(!qu.empty())
{
high ++;
int size = qu.size();
vector<int> temp;
for(int i=0;i<size;i++)
{
TreeNode * node = qu.front();
qu.pop();
temp.push_back(node->val);
if(node->left)
qu.push(node->left);
if(node->right)
qu.push(node->right);
}
if(high %2 ==0)
reverse(temp.begin(),temp.end());
res.push_back(temp);
}
return res;
}
};
2.5 第38题 二叉树的深度
题目描述:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
基本思路:
本题最简单的思路是采用层次遍历的方式,当遍历到最下面的层的时候,就是树的深度。
另外,还可以采用深度遍历的方式,递归比较左右子树的高度,选择高的作为树的深度,同时自底向上,每次增加一个深度。
代码实现
层次遍历方式:
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
int high = 0;
if(pRoot == NULL)
return high;
queue<TreeNode *> qu;
qu.push(pRoot);
while(!qu.empty())
{
high ++;
int size = qu.size();
for(int i = 0;i<size;i++)
{
TreeNode * node = qu.front();
qu.pop();
if(node->left)
qu.push(node->left);
if(node->right)
qu.push(node->right);
}
}
return high;
}
};
深度遍历的方式
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot == NULL)
return 0;
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
return max(left,right) + 1;
}
};
2.5 第23题 二叉树中和为某一个值的路径
题目描述:
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路分析:
一般情况下,如果是需要遍历到所有的结果,最为一般的方法是回溯法,在本题中,我们考虑一下这样几种情况:
- 当遍历到树的根节点之后,如果是目标值的路径,则输出该路径中的结果。
- 当遍历到树的根节点之后,如果不是目标值的路径,则不保存路径的结果。
- 回溯,弹出当前路径的节点,换下一个节点进行遍历。
代码实现:
class Solution {
public:
vector<int> path;
vector<vector<int>> res;
void find(TreeNode * root,int sum)
{
if(!root)
return ;
path.push_back(root->val);
if(root->left==NULL && root->right == NULL && sum == root->val)
{
res.push_back(path);
}
if(root->left)
find(root->left,sum-root->val);
if(root->right)
find(root->right,sum-root->val);
path.pop_back();
}
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
if(!root)
return res;
find(root,expectNumber);
return res;
}
};
2.5 第56题 二叉树的下一个节点
题目描述:
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
基本思路:
这道题需要我们对中序遍历有一个更深的理解,为了便于展示,我们简单的给出一个图:
对于上面的图,我们中序遍历的结果为:
4
,
7
,
2
,
5
,
1
,
3
,
6
4,7,2,5,1,3,6
4,7,2,5,1,3,6
我们分这样几种情况:
- 第一种情况,假设当前的节点为2,则中序遍历的结果的下一个节点为5。
- 第二种情况:假设当前的节点为5,则中序遍历的结果的下一个节点为1。
- 第三种情况:假设当前的节点为6,则中序遍历的结果的下一个结果是空。
根据上面的三种情况,我们不妨分析一下,对于当前节点而言,如果其右子节点不为空,则后一个节点从其右子节点开始找,找到其最后的左子节点。如果不存在右子节点,那么就需要寻找其父节点,如果当前节点是父节点的左子节点,返回的是其父节点,否则就需要继续向上找,直到当前节点是其父节点的左子节点。
代码实现:
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == NULL)
return NULL;
if(pNode->right != NULL)
{
TreeLinkNode * cur = pNode->right;
while(cur->left != NULL)
cur = cur->left;
return cur;
}
else
{
while(pNode->next != NULL)
{
TreeLinkNode * cur = pNode->next;
if(cur->left == pNode)
return cur;
pNode = cur;
}
}
return NULL;
}
};
3、二叉树的结构问题
3.1 第17题 树的子结构
题目描述:
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
基本思路:
首先,我们要明确,子结构是指对应的位置的值是相同的,根据这条规则,我们很容易就明确,我们可以采用一种递归的方式来处理这个问题,只要比节点的值是否相同即可。
代码实现:
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if((!pRoot1) || (!pRoot2))
return false;
bool res = false;
if(pRoot1->val == pRoot2->val)
res = Has(pRoot1,pRoot2);
if(!res)
res = Has(pRoot1->left,pRoot2);
if(!res)
res = Has(pRoot1->right,pRoot2);
return res;
}
bool Has(TreeNode * root1,TreeNode * root2)
{
if((!root1) && (!root2))
return true;
if(!root1)
return false;
if(!root2)
return true;
if(root1->val != root2->val)
return false;
return Has(root1->left,root2->left) && Has(root1->right,root2->right);
}
};
3.2 第18题 二叉树的镜像
题目描述:
操作给定的二叉树,将其变换为源二叉树的镜像。
基本思路:
所谓的镜像二叉树就是将整个二叉树的所有左右节点进行交换。根据这个概念,我们不难想象这个是一个递归的过程。只需要沿着根节点左右交换即可。
代码实现:
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot)
{
TreeNode * node = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = node;
if(pRoot->left)
Mirror(pRoot->left);
if(pRoot->right)
Mirror(pRoot->right);
}
}
};
3.3 第28题 对称的二叉树
题目描述:
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
基本思路:
根据题目的描述,我们可以知道二叉树与二叉树的镜像相同的时候,我们定义的其是对称的,首先对二叉树进行一次遍历,然后将二叉树转换成其镜像树,再次遍历一次,如果两次遍历的结果对应的位置是一致的,那么说明该二叉树是对称的。
代码实现:
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
if(!pRoot)
return true;
vector<int> res1;
vector<int> res2;
preVisit(pRoot,res1);
mirror(pRoot);
preVisit(pRoot,res2);
for(int i=0;i<res1.size();i++)
if(res1[i] != res2[i])
return false;
return true;
}
void preVisit(TreeNode *root,vector<int> &res)
{
if(root)
{
res.push_back(root->val);
preVisit(root->left,res);
preVisit(root->right,res);
}
else
res.push_back(-1);
}
void mirror(TreeNode *root)
{
if(root)
{
TreeNode * node = root->left;
root->left = root->right;
root->right = node;
if(root->left)
mirror(root->left);
if(root->right)
mirror(root->right);
}
}
};
4、二叉搜索树与平衡二叉树
二叉搜索树,也称为二叉查找树,其特点是左子节点<根节点<右子节点。
二叉平衡树:左右子树的高度差小于1。
4.2 第23题 二叉搜索树的后序遍历
题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
基本思路:
首先,对于一个二叉树的后序遍历而言,序列的末尾是二叉的树的根节点,根据根节点,我们可以将整个序列分成左右两个部分,左部分的元素均小于根节点,右部分的值都大于根节点。如果右部分中存在小于根节点的元素,则说明不是二叉树的后序遍历的序列。
代码实现:
class Solution {
public:
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size() == 0)
return false;
return judge(sequence);
}
bool judge(vector<int> sequence)
{
if(sequence.size() == 0)
return true;
int root = sequence[sequence.size()-1];
int middle = 0;
while(sequence[middle]<root)
middle ++;
vector<int> left,right;
for(int j=middle;j<sequence.size()-1;j++)
if(sequence[j]<root)
return false;
for(int j=0;j<middle;j++)
left.push_back(sequence[j]);
for(int j=middle;j<sequence.size()-1;j++)
right.push_back(sequence[j]);
if(judge(left) && judge(right))
return true;
else
return false;
}
};
4.2 第26题 二叉搜索树与双向链表
题目描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
基本思路:
对于一个二叉搜索树而言,其中序遍历就是一个排好序的列表,为了实现一个双向的排序链表,只需要在增加一个临时的指针,保存前一个节点,在让当前节点指向前一个节点即可。同时在返回的时候,也需要返回整棵树的最左节点。
代码实现:
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree == NULL)
return NULL;
TreeNode *pre = NULL;
Con(pRootOfTree,pre);
TreeNode * cur = pRootOfTree;
while(cur->left)
cur = cur->left;
return cur;
}
void Con(TreeNode *root,TreeNode *&pre)
{
if(!root)
return ;
Con(root->left,pre);
root->left = pre;
if(pre)
pre->right = root;
pre = root;
Con(root->right,pre);
}
};
4.3 二叉搜索树的第K个节点
题目描述:
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
基本思路:
对二叉树进行中序遍历,形成排序序列,返回对应位置的元素
代码实现:
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot == NULL||k<=0)
return NULL;
vector<TreeNode *> res;
middleFind(pRoot,res);
if(k>res.size())
return NULL;
else
return res[k-1];
}
void middleFind(TreeNode * root,vector<TreeNode *> & res)
{
if(!root)
return ;
middleFind(root->left,res);
res.push_back(root);
middleFind(root->right,res);
}
};
4.3 平衡二叉树
题目描述:
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
思路分析:
只需要判断左右子树的差值即可。
代码实现:
class Solution {
public:
bool IsBalanced(TreeNode *root,int &dep)
{
if(root == NULL)
return true;
int left = 0;
int right = 0;
if(IsBalanced(root->left,left) && IsBalanced(root->right,right))
{
int dif = left - right;
if(dif <-1 || dif>1)
return false;
dep = (left > right ? left:right) + 1;
return true;
}
return false;
}
bool IsBalanced_Solution(TreeNode* pRoot) {
int dep = 0;
return IsBalanced(pRoot,dep);
}
};