为了巩固我们对于二叉树的理解本篇文章我们来练习一些二叉树的OJ题
一,根据二插入创建字符串
本题链接:根据二叉树创建字符串
1.2解析
根据上述本题可以转换成三句话
- 创建字符串
- 加入括号
- 删除多余括号
前面两步都比较简单,我们以第一个例子来看,
没有删除括号的是这样的,现在我们要开始删括号,删括号就分为了左右,转化一下思路,删括号==有条件的加括号
对于左树来说,左数不为空要加括号,
左数不为空分为了 - 右树为空
- 右树不为空
if(root->left || root->right)//这就涵盖了上面的两种
而对于右树来说就是,右树不为空就要加括号
1.3代码
class Solution {
public:
string tree2str(TreeNode* root) {
if(root==nullptr)
return "";
string str=to_string(root->val);
//去括号
//1.左右都为空
//2.右为空
if(root->left!=nullptr||root->right!=nullptr)//左为空or左不为空,右为空
{
str+='(';
str+=tree2str(root->left);
str+=')';
}
if(root->right!=nullptr)
{
str+='(';
str+=tree2str(root->right);
str+=')';
}
return str;
}
};
二,二叉树的最近公共祖先
本题链接:二叉树的最近公共祖先
2.2解析
2.3代码
class Solution {
public:
bool IsInTree(TreeNode* root, TreeNode* x)
{
if(root==nullptr)
return false;
return root==x
||IsInTree(root->left,x)
||IsInTree(root->right,x);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(p==root||q==root)
return root;
bool qInleft,qInright,pInleft,pInright;
qInleft=IsInTree(root->left,p);
qInright=!qInleft;
pInleft=IsInTree(root->left,q);
pInright=!pInleft;
//一个在左一个在右
//两个都在左
//两个都在右
if((qInleft && pInright)||(qInright && pInleft))
{
return root;
}
else if(qInleft && pInleft)
{
return lowestCommonAncestor(root->left,p,q);
}
else if(qInright && pInright)
{
return lowestCommonAncestor(root->right,p,q);
}
return NULL;
}
};
2.4第二种方法
我们可以记录下从根到p,q的路径,然后把他们各自的路径保存在栈中,栈中最先相等的值就是他们的最近公共祖先。
例如上图,我们取出了路径之后,让他们长的先走知道长度相同再一起走,直到出的值相同就得出祖先了.
class Solution {
public:
bool GetPath(TreeNode* root, TreeNode* x,stack<TreeNode*>&Path)
{
if(root==nullptr)
return false;
Path.push(root);
if(root==x)
return true;
if(GetPath(root->left,x,Path))
return true;
if(GetPath(root->right,x,Path))
return true;
//走到这就是都没有
Path.pop();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
stack<TreeNode*> Path1,Path2;
GetPath(root,p,Path1);
GetPath(root,q,Path2);
//大的先走
while(Path1.size()!=Path2.size())
{
if(Path1.size()>Path2.size())
{
Path1.pop();
}
else
{
Path2.pop();
}
}
while(Path1.top()!=Path2.top())
{
Path1.pop();
Path2.pop();
}
return Path1.top();
}
};
三,二叉搜索树与双向链表
本题链接: 二叉树搜索树转换成排序双向链表
3.2解析
当前节点(cur)的左指向前一个节点
前一个节点(prev)的右指向当前节点
再把cur给prev向上走
3.3代码
class Solution {
public:
void InOrderConvert(TreeNode*cur,TreeNode*&prev)
{
if(cur==nullptr)
return;
InOrderConvert(cur->left,prev);
//当前节点的左指向前一个节点
cur->left=prev;
//前一个节点的右指向当前节点
if(prev)
prev->right=cur;
prev=cur;
InOrderConvert(cur->right,prev);
}
TreeNode* Convert(TreeNode* pRootOfTree) {
TreeNode*prev=nullptr;
InOrderConvert(pRootOfTree,prev);
TreeNode* head=pRootOfTree;
while(head->left&&head)
{
head=head->left;
}
return head;
}
};
四,从前序与中序遍历序列构造二叉树
本题链接:从前序与中序遍历序列构造二叉树
4.2解析
前序:根,左子树,右子树
中序:左子树,根,右子树
前序确定根,中序确定左右子树
3为根,那么他的左右子树就确定了下来。
3的左边界为[0,1-1=0],右边界为[1+1=2,4]
同理我们在看9,
9的左边界就是[0,0-1=-1],右边界就是[0+1=1,0],很显然边界错误。
所以我们判断它是否有左右子树的条件就出来了。
我们用prei走前序,然后用rooti表示在中序中找到指定的根,找到了rooti的位置我们也就成功的把中序分成了三个部分,inbegin和inend就用来表示边界。
代码中的注释也可以方便理解。
4.3代码
class Solution {
public:
TreeNode*_buildTree(vector<int>& preorder, vector<int>& inorder,int&prei,int inbegin,int inend)
{
if(inbegin>inend)
return nullptr;
TreeNode*root=new TreeNode(preorder[prei++]);
//找到中序中指定的根
int rooti=inbegin;
while(rooti<=inend)
{
if(inorder[rooti]==root->val)
break;
else
rooti++;
}
//此时我们就成功把中序分成了三个部分
//[inbegin,rooti-1] rooti [rooti+1,inend]
root->left=_buildTree(preorder, inorder,prei,inbegin,rooti-1);
root->right=_buildTree(preorder, inorder,prei,rooti+1,inend);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int i=0;
TreeNode* root=_buildTree(preorder, inorder,i,0,preorder.size()-1);
return root;
}
};
五,前序,中序,后序遍历(非递归)
5.1前序非递归
本题链接:
1 前序遍历非递归
5.1.2解析
前序遍历:根->左子树->右子树;我们先遍历左路节点,把他们存到栈中,然后取出各节点遍历右树。
5.1.3代码
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*>s;
vector<int> v;
TreeNode* cur=root;
while(cur||!s.empty())
{
//取左路节点
while(cur)
{
v.push_back(cur->val);
s.push(cur);
cur=cur->left;
}
//获取左路节点
TreeNode*top=s.top();
s.pop();
//遍历子问题
cur=top->right;
}
return v;
}
};
5.2中序非递归
5.2.2解析
这个和前序基本一样,但是要注意插入的位置,因为是先左子树再根,所以我们的vector的位置是在左路节点都进入栈之后才开始的。
5.2.3代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> v;
TreeNode* cur=root;
while(cur||!s.empty())
{
//遍历左节点
while(cur)
{
s.push(cur);
cur=cur->left;
}
//获取左节点
TreeNode* top=s.top();
s.pop();
//当我们取栈里面的数的时候,左子树就访问完了
v.push_back(top->val);
//开始遍历右树——子问题
cur=top->right;
}
return v;
}
};
5.3后续非递归
3 后续遍历非递归
5.3.2解析
后续更麻烦一些,例如上图我们还是先访问左路径,6右为空,返回6,到5的时候不能返回,因为他还有右子树,第二次到五才能返回,因为他的右子树访问结束了
所以我们可以有一个prev来记录上一个访问的节点,如果上一个访问的节点为现在这个节点的右子树说明他已经结束了
5.3.3代码
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> v;
TreeNode* cur=root;
TreeNode* prev=nullptr;
while(cur||!s.empty())
{
//遍历左节点
while(cur)
{
s.push(cur);
cur=cur->left;
}
//获取左节点
TreeNode* top=s.top();
//当我们取栈里面的数的时候,左子树就访问完了
//右子树为空,或top的右为上一个访问的节点
if(top->right==nullptr||top->right==prev)
{
v.push_back(top->val);
s.pop();
prev=top;
}
else
{
//开始遍历右树——子问题
cur=top->right;
}
}
return v;
}
};