一、根据二叉树创建字符串
题目:
思路:这个题很明显需要我们采用二叉树的递归实现(前序遍历),但有一个注意的点:空括号能不能省略的问题,其实我们发现只要左为空,右不为空不能省略括号,剩下情况均可省略。
代码如下:
class Solution {
public:
string tree2str(TreeNode* root) {
string str;
if(root==nullptr)
{
return str;
}
str+=to_string(root->val);//转换字符串函数
//左不为空不能省略括号
//左空右不空同样也不能省略(反映树的关系)
//只有左右都空可省略
if(root->left||root->right)
{
str+="(";
str+=tree2str(root->left);
str+=")";
}
//只要右为空即省略
if(root->right)
{
str+="(";
str+=tree2str(root->right);
str+=")";
}
return str;
}
};
二、二叉树的分层遍历1
题目:给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
这个与我们前面的二叉树初阶部分的层序遍历相似,但不同的是,它要把每层数据分别存储成一个二维数组,我们之前的思路是创建两个队列,分别存储该节点及其孩子,等该节点pop掉又会把他们的孩子带进来。
本题思路:创建一个vector<vector<int>>,再创建一个队列以及变量levelsize(记录每层多少个数据),队列存储的是该节点的指针(并不是该节点数据)每次pop掉以后插入到vector变量中,最后把每次的vector 插入到vector<vector<int>>即可。
代码如下:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
queue<TreeNode*>q;
int levelsize=0;
if(root)
{
q.push(root);
levelsize=1;
}
while(levelsize>0)
{
//一层一层出
vector<int>v;
while(levelsize--)
{
TreeNode* front=q.front();
q.pop();
v.push_back(front->val);
//下一层的孩子入队列
if(front->left)
{
q.push(front->left);
}
if(front->right)
{
q.push(front->right);
}
}
vv.push_back(v);//一层层的插入
levelsize=q.size();//下一层孩子的节点个数
}
return vv;
}
};
三、二叉树的分层遍历2
题目,与分层遍历1相同,只不过是从底向上遍历。
思路:在二的代码的基础上逆置一下即可,即在return vv前加一个reverse(vv.begin(),vv.end());代码略
四、二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
比如:5和4的公共祖先是5(自己也可以是祖先),6和4的公共祖先是5,2和8的公共祖先是3,我们能够看出一个规律:除了自己是自己祖先的情况下,剩下的情况一个在祖先的左子树,一个在右子树(只有最近祖先满足这个规律!)
代码如下:
class Solution {
public:
bool isintree(TreeNode* t, TreeNode* x)//判断是否在子树中
{
if(t==nullptr)
return false;
return t==x||isintree(t->left,x)||isintree(t->right,x);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==nullptr)
{
return nullptr;
}
if(root==p||root==q)//如果自己就是祖先直接返回即可
{
return root;
}
bool pinleft=isintree(root->left,p);
bool pinright = !pinleft;
bool qinleft=isintree(root->left,q);
bool qinright = !qinleft;
if((pinleft&&qinright)||(qinleft&&pinright))//孩子分别在祖先的两侧就是祖先
{
return root;
}
else if(pinleft&&qinleft)//右子树没有了直接去左子树找形成新的树
return lowestCommonAncestor(root->left,p,q);
else
return lowestCommonAncestor(root->right,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*> ppath,qpath;
getpath(root,p,ppath);
getpath(root,q,qpath);
//让长的先走,对齐
while(ppath.size()!=qpath.size())
{
if(ppath.size()>qpath.size())
{
ppath.pop();
}
else
{
qpath.pop();
}
}
//一起走,找节点
while(ppath.top()!=qpath.top())
{
ppath.pop();
qpath.pop();
}
return ppath.top();
}
};
五、二叉搜索树转换成双向链表
我们提供一个简单的思路:中序遍历二叉树,将二叉树的结点存在vector容器中,再把结点的链接关系进行更改即可。在此我们就不用代码演示了。
我们来看下一种思路:依旧是中序遍历二叉树,遍历过程中修改左指针为前驱和右指针为后继指针。记录一个cur和prev,cur为当前中序遍历到的结点,prev为上一个中序遍历的结点,cur->left=prev,但我们不知道cur->right指向哪里,但我们可以让prev->right=cur,也就是说,每个结点的左是再中遍历到当前结点时修改指向前驱,但当前结点的右,是在遍历到下一个结点才可修改后继的。
代码如下:
class Solution {
public:
void inorderconvert(TreeNode*cur,TreeNode*&prev)
{
if(cur==nullptr)
return ;
inorderconvert(cur->left,prev);
//中序cur
//左为前驱
cur->left=prev;
//右为后驱
if(prev)
prev->right=cur;
prev=cur;
inorderconvert(cur->right,prev);
}
TreeNode* Convert(TreeNode* root) {
if(root==nullptr)
return nullptr;
TreeNode* prev=nullptr;
inorderconvert(root, prev);
TreeNode*head=root;
while(head->left)
{
head=head->left;
}
//循环链表,头尾相接
head->left=prev;
prev->right=head;
return head;
}
};
我们发现,以上所有的题目貌似都是用递归的思想去实现代码的,接下来我们来实现一下非递归的思想。
六、二叉树的前、中、后序遍历(非递归)
前序:先访问左路结点,再访问左路结点的右子树,访问右子树要以循环从栈依次取出这些结点,循环子问题的思想访问左路结点的右子树,所以我们需要用一个栈和循环实现。
代码如下:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*>st;
vector<int> v;
TreeNode*cur=root;
while(cur||!st.empty())
{
//每次循环代表访问一棵树的开始
while(cur)
{
v.push_back(cur->val);
st.push(cur);
cur=cur->left;
}
//左走到头了找出最后一个左的右开始访问
TreeNode* top=st.top();
st.pop();
//循环访问右子树
cur=top->right;
}
return v;
}
};
中序:与前序类似,只是我们先入栈不访问,等到左子树走完了以后我们再进行访问,只需要在前序的代码上稍作修改即可
后序:与之前相比麻烦一些,取到一个左路结点时,左子树是否已经访问过了呢?需要我们进行区分,否则程序会进入死循环,如果左路结点的右子树不为空,右子树没有访问,那么上一个访问节点是左子树的根,如果左路节点右子树不为空,右子树已经访问过了,那么上一个访问的是右子树的根。
代码如下:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*>st;
vector<int> v;
TreeNode*prev=nullptr;
TreeNode*cur=root;
while(cur||!st.empty())
{
//每次循环代表访问一棵树的开始
while(cur)
{
st.push(cur);
cur=cur->left;
}
//取一个左路节点的右子树出来访问,这时代表左路节点的左子树已经访问过了
TreeNode* top=st.top();
//右子树为空或者上一个访问的节点是右子树的根,代表右子树也访问过了。
if(top->right==nullptr||top->right==prev)
{
v.push_back(top->val);
st.pop();
prev=top;
}
else
{
cur=top->right;
}
}return v;
};