博主的博客主页:CSND博客
Gitee主页:博主的Gitee
博主的稀土掘金:稀土掘金主页
博主的b站账号:程序员乐
公众号——《小白技术圈》,回复关键字:学习资料。小白学习的电子书籍都在这。
目录
- 根据二叉树创建字符串
- 二叉树的层序遍历
- 二叉树的最近公共祖先
- 二叉搜索树与双向链表
- 从前序与中序遍历序列构造二叉树
- 从中序与后序遍历序列构造二叉树
根据二叉树创建字符串
题目的意思就是前序遍历这个二叉树,遍历的结果是字符串的形式,该到题目的主要问题就是小括号的问题
()
的问题。
- 我们写这个题目还是要用到递归的思想:
- 处理当前逻辑:如果节点不为空,把该值转成字符插入到字符串中;如果为空,就返回空字符串
- 处理子树:对于子树我们要考虑括号的问题,处理号括号问题,子树我们也可以看出一个树,所以直接把子树遍历的结果加到字符串就可以了。
- 什么时候加小括号呢?
左子树什么加括号?——左子树不为空的时候,一定要加;左子树为空的时候,右子树不为空的时候,一定要加括号
右子树什么时候加括号呢?——右子树不为空就加
- 返回遍历完成的字符串
class Solution {
public:
string tree2str(TreeNode* root) {
string str;
//当前逻辑
if(root)
str+=to_string(root->val);
else
return string();
//左树
if(root->left||root->right)
{
str+='(';
str+=tree2str(root->left);
str+=')';
}
//右树
if(root->right)
{
str+='(';
str+=tree2str(root->right);
str+=')';
}
return str;
}
};
我们这样写虽然可以,但是要注意到,每次返回的时候都有
string
的拷贝,这个代价是非常大的,所以我们可以优化一下。我们写一个子函数,然后用引用返回。就可以减少拷贝了。因为传的是引用,所以不需要在返回字符串了,因为直接就添加到字符串中去了。
class Solution {
void f(string& str,TreeNode* root)
{
if(root)
str+=to_string(root->val);
else
return ;
//左树
if(root->left||root->right)
{
str+='(';
f(str,root->left);
str+=')';
}
//右树
if(root->right)
{
str+='(';
f(str,root->right);
str+=')';
}
}
public:
string tree2str(TreeNode* root) {
string ret;
f(ret,root);
return ret;
}
};
二叉树的层序遍历
层序遍历我们会的,那就是用一个队列储存每一层的节点,当出队列的同时,把该节点的左右孩子节点也入队列。
但是本题目与我们之前做的题目有点稍微的不同,就是我们返回的是一个二维数组,每一层的元素构成一维数组存在二维数组中。
- 怎么解决这个问题呢?
用一个值去记录每一层节点的数目,出队列的时候,把节点数加一,当节点数为0的时候,说明该层节点遍历完成;下一层的节点的数目就是队列元素的个数。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
queue<TreeNode*> q;
int sum=1;
if(root)
q.push(root);
vector<int> v;
while(!q.empty())
{
TreeNode* cur=q.front();
q.pop();
v.push_back(cur->val);
sum--;
if(cur->left)
q.push(cur->left);
if(cur->right)
q.push(cur->right);
if(sum==0)
{
sum=q.size();
vv.push_back(v);
v.clear();
}
}
return vv;
}
};
二叉树的最近公共祖先
这道题目我们可以用交叉链表的思想去做,我们把每个节点所在的路径找出来,两个路径相交的第一个节点就是他的最近公共祖先。对于该路径我们用栈进行储存下来。我们用中序遍历的方式去找路径。
- 找路径的过程
- 如果当前节点不是要查找的节点,去找左树,直到查找到空的时候还没有找到,那就删除左树;去找右树,找到了就是该节点,找不到把该节点从栈中删除,在从栈顶的左子树开始查找。
class Solution {
bool Find(TreeNode* root,TreeNode* airm,stack<TreeNode*>& st)
{
st.push(root);
if(root==nullptr)
return false;
if(root==airm)
{
return true;
}
if(!Find(root->left,airm,st))
{
st.pop();
root=st.top();
}
else
return true;
if(!Find(root->right,airm,st))
{
st.pop();
root=st.top();
}
else
return true;
return false;
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
stack<TreeNode*> pst,qst;
Find(root, p,pst);
Find(root,q,qst);
int i=pst.size()>qst.size()?pst.size()-qst.size():qst.size()-pst.size();
while(i--)
{
if(pst.size()>qst.size())
pst.pop();
else
qst.pop();
}
while(qst.top()!=pst.top())
{
pst.pop();
qst.pop();
}
return pst.top();
}
};
- 另一种方法
去找那两个节点在哪里——是左子树还是右子树。
如果两个节点一个在左子树,另一个在右子树,那么最近公共祖先就是根节点。
如果两个都在同一个子树里面,那么就可以拆分成一个子问题,把子树看成一个树。
特别注意的是,当其中一个节点是该树的根节点的时候,那个该最近公共祖先就是这样该节点。
class Solution {
bool Find(TreeNode* root,TreeNode* cur)
{
if(root==nullptr)
return false;
if(root==cur)
return true;
return Find(root->left,cur)||Find(root->right,cur);
}
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==p||root==q)
return root;
bool p_left=false,p_right=false;
bool q_left=false,q_right=false;
//确定在哪个子树
if(!Find(root->left,p))
p_right=true;
else
p_left=true;
if(!Find(root->left,q))
q_right=true;
else
q_left=true;
if((p_left&&q_right)||(p_right&&q_left))
return root;
else if(p_left&&q_left)
return lowestCommonAncestor(root->left,p,q);
else if(p_right&&q_right)
return lowestCommonAncestor(root->right,p,q);
else
return nullptr;
}
};
二叉搜索树与双向链表
把一个二叉树变成一个有序的双向链表,首先有序是很简单的,因为二叉搜索树的中序遍历就是有序的,那么把二叉树进行中序遍历,然后改变节点的指向。
我们用两个指针,一个指针cur
指向当前节点的位置,另一个指针pre
指向中序遍历过程中当前节点的上一个节点(该节点要设成引用,因为要保证该节点在递归过程中是不变的)。
cur->left=pre
,pre->right=cur
,然后pre
要到当前cur
的位置。
class Solution {
void inorder(TreeNode* root,TreeNode* & pre)
{
if(root==nullptr)
return ;
inorder(root->left, pre);
root->left=pre;
if(pre)
{
pre->right=root;
}
pre=root;
inorder(root->right, pre);
}
public:
TreeNode* Convert(TreeNode* pRootOfTree) {
if(!pRootOfTree)
return nullptr;
TreeNode* pre=nullptr;
TreeNode* root=pRootOfTree;
while(root->left)
root=root->left;
inorder(pRootOfTree,pre);
return root;
}
};
从前序与中序遍历序列构造二叉树
前序遍历是确定了根节点,中序遍历把根节点分成左右子树。
对于该到题目,我们把前序遍历的每一个元素看成一个根节点,然后利用中序遍历把左右子树确定,而左右子树又可以用该方法确定——子树递归即可。
class Solution {
TreeNode* create(vector<int>& preorder,vector<int>& inorder,int& i,int L,int R)
{
if(L>R||L<0||R<0)
return nullptr;
TreeNode* root=new TreeNode(preorder[i]);
int mid;
for(mid=L;mid<=R;mid++)
{
if(preorder[i]==inorder[mid])
break;
}
i++;
root->left=create(preorder,inorder,i,L,mid-1);
root->right=create(preorder,inorder,i,mid+1,R);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int i=0;
return create(preorder,inorder,i,0,preorder.size()-1);
}
};
从中序与后序遍历序列构造二叉树
该道题目和上一道题目差不多,只不过后序遍历的最后访问根节点。所以我们只需要把上道题目的遍历逆过来就行。注意:该题目先处理右树,在处理左树,因为后序遍历从后往左为:根,右,左。
class Solution {
TreeNode* create(vector<int>& postorder,vector<int>& inorder,int & i,int L,int R)
{
if(L>R||L<0||R<0)
return nullptr;
TreeNode* root=new TreeNode(postorder[i]);
int mid;
for(mid=L;mid<=R;mid++)
{
if(postorder[i]==inorder[mid])
break;
}
i--;
root->right=create(postorder,inorder,i,mid+1,R);
root->left=create(postorder,inorder,i,L,mid-1);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int i=inorder.size()-1;
return create(postorder,inorder,i,0,inorder.size()-1);
}
};