文章目录
- 一、根据二叉树创建字符串
- 二、二叉树的最近公共祖先
- 1.解法一:递归
- 2.解法二:借助栈来寻找路径
- 三、二叉搜索树与双向链表
- 四、前序与中序构建二叉树
- 五、中序与后序构建二叉树
一、根据二叉树创建字符串
题目链接:力扣第606题:根据二叉树创建字符串
解析:
这道题主要是关于二叉树的前序遍历,题目要求我们的每一个子树都要用括号括起来,然后要求最终要删除重复的括号,那么我们先来解决最基本的,将括号都括起来再说,然后再讨论如何去除多余括号
我们很容易想到如下的方法可以先将每个都带上括号。就是一个先序遍历,需要注意的就是to_string这个函数也许不是那么的常见,它的作用是将一个整型转换为string类型的。
我们先观察一下测试结果,跟我们预期是一样的。现在主要关心的是除去多余的括号
对于除去多余的括号,我们可以先讨论一下,何时加上括号?根据原题的样例,我们不难得知
- 如果左子树不为空且右子树为空,可以省略右子树的括号,只要输出左子树的括号
- 如果左子树为空且右子树不为空,不可以省略左子树的括号,左右都要输出
- 如果两个子树都为空,两个括号都要省略,都不输出左右子树的括号
- 如果两个都不为空,两个都不省略,左右都要输出
上面的四条规则,根据我们现有的代码,我们可以考虑何时不省略括号的条件即可,只要当不省略的条件满足,就正常加入括号即可。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {if(root==nullptr){return "";}string str;str+=to_string(root->val);if((root->left&&root->right==nullptr)||(root->left&&root->right)||(root->left==nullptr&&root->right)){str+='(';str+=tree2str(root->left);str+=')';}if((root->left==nullptr&&root->right)||(root->left&&root->right)){str+='(';str+=tree2str(root->right);str+=')';}return str;}
};
上面的代码是可以通过的,不过上面的条件显得过于臃肿了,我们不妨简化一下
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {if(root==nullptr){return "";}string 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;}
};
二、二叉树的最近公共祖先
力扣链接:力扣第236题:二叉树的最近公共祖先
解析:
对于这道题,如果这道题是一个三叉链的话,就很简单了,因为就相当于退化为了两个链表的相交问题,此时我们直接计算长度,然后使用快慢指针即可。这样就很简单了。不过可惜的是这道题是二叉链的。所以上面的想法是不可以的。
1.解法一:递归
这道题,我们先来画图熟悉一下公共祖先的几种可能性
如上图所示,所有的可能性就两种,一种是如果是4和6的情况下,他们所处的路径上,最近的就是5,此时我们会注意到,对于5这个结点而言,一个在左边,一个在右边。如果是更远一点的公共祖先的话,就不满足这种情况了。要么都在祖先的左边,要么都在祖先的右边。通过这一点,我们就知道了一种祖先的判别方式。还有一种情况就是一个结点是另外一个结点的祖先,如由上图所示。这时候满足的条件就是,如果此时root就等于p或者q的话,那么root就是我们的公共祖先了。
依据上面的分析,我们可以写出如下代码
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool FindNode(TreeNode* root, TreeNode* obj){if(root==nullptr){return false;}if(root == obj){return true;}return FindNode(root->left,obj)||FindNode(root->right,obj);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == nullptr){return nullptr;}if(root == p || root == q){return root;}bool pInLeft,pInRight,qInLeft,qInRight;pInLeft = FindNode(root->left, p);pInRight = !pInLeft;qInLeft = FindNode(root->left ,q);qInRight = !qInLeft;if(pInLeft&&qInLeft){return lowestCommonAncestor(root->left,p,q);}else if(pInRight&&qInRight){return lowestCommonAncestor(root->right,p,q);}else {return root;}}
};
时间复杂度分析:
这道题的时间复杂度是O(N²),因为最极端的情况如下所示,即二叉树已经退化为了一个链表,这时候,二叉树高度为N,且由于每次都在一边,这需要N次。在每一次中,又需要判断是不是在左树,这个判断是一个等差数列。所以最终的时间复杂度为O(N²)。不过主要的原因还是在于这是一颗普通的树,假如这棵树是一棵二叉搜索树,那么我们的代价就变小了很多。我们Find的代价直接就没有了,因为根据性质我们就可以知道某个结点是在左子树还是右子树。所以效率变为了O(N)
2.解法二:借助栈来寻找路径
我们一开始想的办法是如果是三叉链的话,那么就可以转化为链表相交的问题,那么如果不是三叉链的话有没有办法能实现类似的思路呢?答案是有的,我们三叉链相比二叉链的优势就是三叉链可以很方便的找到路径。然后根据路径遇到相同的就是最近公共祖先了。
而对于二叉链,我们就只能使用栈来寻找路径了。只要有了路径,那么就可以采用类似的思路了。
所以我们就可以写出如下代码了
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool FindPath(TreeNode* root, TreeNode* obj, stack<TreeNode*>& st){if(root == nullptr){return false;}st.push(root); if(obj == root){return true;}if(FindPath(root->left,obj,st)){return true;}if(FindPath(root->right,obj,st)){return true;}st.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> pst;FindPath(root,p,pst);stack<TreeNode*> qst;FindPath(root,q,qst);while(pst.size()>qst.size()){pst.pop();}while(pst.size()<qst.size()){qst.pop();}while(pst.top()!=qst.top()){pst.pop();qst.pop();}return pst.top();}
};
在这段代码中,尤其是寻找路径这段代码,是非常巧妙的,它是利用了返回值的真假来进行截断的。否则我们很难控制路径的结束。
三、二叉搜索树与双向链表
牛客链接:牛客:二叉搜索树转化为双向链表
解析:
这道题我们最容易想到的办法就是暴力法,即直接用一个栈,将每一个结点都放到栈里面。然后再依次将每个结点链接起来即可。不过很可惜,这道题具有空间复杂度要求。显然不想让我们使用任何容器,想让我们就在原地进行修改链接关系。
/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/
class Solution {
public:void InOrder(TreeNode* root, TreeNode*& prev){if(root == nullptr){return;}InOrder(root->left,prev);root->left = prev;if(prev){prev->right = root; }prev = root;InOrder(root->right,prev);}TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree==nullptr){return nullptr;}TreeNode* ret = pRootOfTree;while(ret->left){ret = ret->left;}TreeNode* prev = nullptr;InOrder(pRootOfTree,prev);return ret;}
};
如上代码所示,既然要是排序的双向链表,那么必然是中序的。在中序的过程中,我们需要改变链接关系,只传一个指针是肯定不够的,虽然我们是无法窥测未来的,但是过去我们是知道的,所以我们应该传一个前驱节点,然后让当前的左指向过去,过去的右指向现在。然后将过去的值赋为现在,就可以继续下一次递归了。
四、前序与中序构建二叉树
力扣链接:力扣第105题:前序与中序构建二叉树
解析:
这道题的思路我们还是比较清楚的,根据前序确定根,然后再中序中确定根所处的位置,分割两个区间,然后递归即可。
但是再一些细节把握上还是需要注意的,递归戒截止的条件应该是当左右不构成区间了那么递归就该结束了。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* build(vector<int>& preorder,vector<int>& inorder,int& previ,int inbegin,int inend){if(inbegin>inend){return nullptr;}TreeNode* newnode = new TreeNode(preorder[previ]);int inpos = inbegin;while(inpos<=inend){if(preorder[previ]==inorder[inpos]){break;}inpos++;}previ++;newnode->left = build(preorder,inorder,previ,inbegin,inpos-1);newnode->right = build(preorder,inorder,previ,inpos+1,inend);return newnode;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int previ = 0;return build(preorder,inorder,previ,0,inorder.size()-1);}
};
五、中序与后序构建二叉树
力扣链接:力扣第106题:中序与后序构建二叉树
解析:
这道题与前面的题基本上是一样的。后序与前序的不同就是我们创建好结点以后,就得先创建右子树了,才能创建左子树。这是因为后序是左子树,右子树,根这种遍历方式的,那么越靠后就先是右子树。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:TreeNode* build(vector<int>& inorder,vector<int>& postorder,int& posti,int inbegin,int inend){if(inbegin>inend){return nullptr;}TreeNode* newnode = new TreeNode(postorder[posti]);int inpos = inbegin;while(inpos<inend){if(postorder[posti]==inorder[inpos]){break;}inpos++;}posti--;newnode->right = build(inorder,postorder,posti,inpos+1,inend);newnode->left = build(inorder,postorder,posti,inbegin,inpos-1);return newnode;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int posti = postorder.size()-1;return build(inorder,postorder,posti,0,inorder.size()-1);}
};