目录
1.根据二叉树创建字符串
给你二叉树的根节点 root
,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。
空节点使用一对空括号对 "()"
表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
输入:root = [1,2,3,null,4]
输出:"1(2()(4))(3)"
解释:无法省略第一个空括号对,否则会破坏输入与输出一一映射的关系。
首先在顺序上符合前序遍历:1 2 4 3
如果在递归遍历子树之前和之后都加括号 预期结果:1 (2() (4()()) ) (3()())
判断省略条件
a. root->right == nullptr 要省略
b. root->left == nullptr 不能省略
c. 在root->right == nullptr 的情况下 root->left == nullptr 都可以省略
总结:右为空 省略,右为空&&左为空 省略
反过来: 右不为空 继续, 左不为空 || 右不为空 继续
class Solution {
public:
string tree2str(TreeNode* root)
{
if(root == nullptr)
return "";
string 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;
}
};
2.二叉树的分层遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。返回二维数组。
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
层序遍历需要借助队列,但是这个题目要求存进一个二维数组里
也就是分层
思路1:
借助两个队列,一个放一层的数据,另一个为空队列。每次都是一个队列的数据出队列后,把左右节点入到另一个空队列。往复循环。
思路2:
一个队列存节点,另一个队列存节点对应的层数,每次同时push 和 pop
思路3:
定义一个整型levelsize,用来记住这一层的节点个数,pop levelsize次 后队列的size就是下一层levelsize
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret;
queue<TreeNode*> q;
if(root == nullptr)
return ret;
int levelsize = 1;
q.push(root);
while(!q.empty())
{
vector<int> tmp;
while(levelsize--)
{
TreeNode* front = q.front();
tmp.push_back(front->val);
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
q.pop();
}
ret.push_back(tmp);
levelsize = q.size();
}
return ret;
}
};
3.二叉树的最近公共祖先*
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点
5
和节点4
的最近公共祖先是节点5 。
因为根据定义最近公共祖先节点可以为节点本身。
通过规律发现,可以分为下面的几种情况
1.p和q分别在左右子树 那么root就是最近公共祖先
2.如果p和q在一边的子树,那么最近公共祖先就在那一边
3.如果root就是p或者q的一个,那么root就是最近公共祖先
思路1:
如果用递归的方式,时间复杂度为O(n*n),直接用递归去找这个过程,多次遍历树
class Solution {
public:
bool have_or_not(TreeNode* root, TreeNode* p, TreeNode* q)
{
if(root == nullptr)
return false;
if(root == p || root == q)
return true;
return have_or_not(root->left,p,q)||have_or_not(root->right,p,q);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == p || root == q)
return root;
bool _left = have_or_not(root->left,p,q);
bool _right = have_or_not(root->right,p,q);
if(_left && _right)
return root;
else if(_left)
return lowestCommonAncestor(root->left, p, q);
else
return lowestCommonAncestor(root->right, p, q);
}
};
思路2:
找到从root到p和q并记录这个路径
然后如果两个路径的长度不相等,长的那一节减去,现在一样长
如果两个路径的最下面不相等,就一直减,直到找到那个最近公共祖先
但要怎么找到从root到p和q并记录这个路径?
遍历能找到,记录交给栈
class Solution {
public:
bool find_path(stack<TreeNode*>& path, TreeNode* root, TreeNode* x)
{
if(root == nullptr)
return false;
path.push(root);
if(root == x)
return true;
if(find_path(path, root->left, x))
return true;
if(find_path(path, root->right, x))
return true;
path.pop();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
stack<TreeNode*> pPath, qPath;
find_path(pPath, root, p);
find_path(qPath, root, q);
int psize = pPath.size();
int qsize = qPath.size();
while(psize > qsize)
{
pPath.pop();
psize--;
}
while(psize < qsize)
{
qPath.pop();
qsize--;
}
while(pPath.top()!=qPath.top())
{
pPath.pop();
qPath.pop();
}
return pPath.top();
}
};
这个栈要引用传递
时间复杂度为O(n)
5. 二叉搜索树转换成排序双向链表*
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
思路1:
简单粗暴递归(后序递归)
如果左子树和右子树都已经处理好,是双向链表了,就只需要链接左右和中间的节点就可以
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree) {
if (pRootOfTree == nullptr)
return nullptr;
TreeNode* _left = Convert(pRootOfTree->left);
TreeNode* _right = Convert(pRootOfTree->right);
//处理左边的
while(_left && _left->right)
_left = _left->right;
pRootOfTree->left = _left;
if(_left)
_left->right = pRootOfTree;
//处理右边的
pRootOfTree->right = _right;
if(_right)
_right->left = pRootOfTree;
while(pRootOfTree->left)
pRootOfTree = pRootOfTree->left;
return pRootOfTree;
}
};
思路2:
中序遍历
我们发现双向链表的顺序和中序遍历的结果一样
问题的关键在于中序遍历到的节点的左指针要指向遍历到的前一个节点,右指针要指向下一个节点(就是在下一个节点,指向这个节点)
现在问题的关键就转变为怎么记录前一个节点
引用 说明只有一份prev
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);
while(pRootOfTree&&pRootOfTree->left)
pRootOfTree = pRootOfTree->left;
return pRootOfTree;
}
};
6. 根据一棵树的前序遍历与中序遍历构造二叉树*
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
发现:前序的第一个数是根节点, 再通过中序找到前序的第一个节点(也就是根节点)把这个分成两个部分,根节点左边的是左子树,右边是右子树
思路1:利用原函数递归的返回值,原函数返回这个构建树的根,左右两边交给递归
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.empty())
return nullptr;
int val = preorder.front();
TreeNode* newnode = new TreeNode(val);
auto it1 = preorder.begin() + 1;
auto it2 = inorder.begin();
int count = 0;
while (*it2 != val)
{
++it2;
++count;
}
it1 += count;
vector<int> left1(preorder.begin() + 1, it1);
vector<int> right1(it1, preorder.end());
vector<int> left2(inorder.begin(), it2);
vector<int> right2(it2 + 1, inorder.end());
newnode->left = buildTree(left1, left2);
newnode->right = buildTree(right1, right2);
return newnode;
}
缺点:空间消耗大O(n*logn)
思路2:
把一个区间稳定选取出一份区间(在任意位置),需要两个指针维护(begin,end)
知道开头和结尾,把一个区间分成两份需要一个指针(inrooti)
怎么样才能找到这个中序的inroot呢,之前是需要前序的第一个位置
方法一:计算rooti
left :rooti+1
right : rooti +1+ count(inrooti-begin)
方法二:观察rooti
现在已知前序序列,在前序递归是可以到每一个根节点,使用引用就可以
TreeNode* _buildTree2(vector<int>& preorder, vector<int>& inorder, int& rooti, int inbegin, int inend)
{
if (inbegin == inend)
{
++rooti;
return new TreeNode(inorder[inbegin]);
}
if (inbegin > inend)
return nullptr;
TreeNode* newnode = new TreeNode(preorder[rooti]);
int inroot = inbegin;
while (preorder[rooti] != inorder[inroot])
{
++inroot;
}
++rooti;
newnode->left = _buildTree2(preorder, inorder, rooti, inbegin, inroot - 1);
newnode->right = _buildTree2(preorder, inorder, rooti, inroot + 1, inend);
return newnode;
}
TreeNode* buildTree2(vector<int>& preorder, vector<int>& inorder) {
if (preorder.empty())
return nullptr;
int rooti = 0;
return _buildTree2(preorder, inorder, rooti, 0, preorder.size() - 1);
}
7. 根据一棵树的中序遍历与后序遍历构造二叉树
和上面差不多
TreeNode* _buildTree3(vector<int>& inorder, vector<int>& postorder, int& rooti, int inbegin, int inend)
{
if (inbegin == inend)
{
--rooti;
return new TreeNode(inorder[inbegin]);
}
if (inbegin > inend)
return nullptr;
TreeNode* newnode = new TreeNode(postorder[rooti]);
int inroot = inbegin;
while (postorder[rooti] != inorder[inroot])
{
++inroot;
}
--rooti;
newnode->right = _buildTree3(inorder, postorder, rooti, inroot + 1, inend);
newnode->left = _buildTree3(inorder, postorder, rooti, inbegin, inroot - 1);
return newnode;
}
TreeNode* buildTree3(vector<int>& inorder, vector<int>& postorder) {
if (inorder.empty())
return nullptr;
int rooti = postorder.size() - 1;
return _buildTree3(inorder, postorder, rooti, 0, postorder.size() - 1);
}
8. 二叉树的前序遍历,非递归迭代实现
思路1:(简单)
用栈,先进一个根,接下来出栈顶,先进右后进左,因为下面左边的就在栈顶,就先出栈
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
stack<TreeNode*> st;
if(root == nullptr)
return v;
st.push(root);
while(!st.empty())
{
TreeNode* top = st.top();
st.pop();
v.push_back(top->val);
if(top->right)
st.push(top->right);
if(top->left)
st.push(top->left);
}
return v;
}
};
思路2:*
访问一棵树: 1.左路节点 2.左路节点的右子树
前序遍历,访问到左路节点入栈入vector,一路访问到底,说明栈顶节点的左为空
接下来访问右子树,当前栈顶就可以pop了
如果右子树为空,再取栈顶节点的右子树。
结束条件,栈为空,还有右树要处理
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> v;
if(root == nullptr)
{
return v;
}
TreeNode* cur = root;
while(cur || !st.empty())
{
while(cur)
{
st.push(cur);
v.push_back(cur->val);
cur = cur->left;
}
TreeNode* top = st.top();
st.pop();
cur = top->right;
}
return v;
}
};
9. 二叉树中序遍历 ,非递归迭代实现
思路1:
先放左节点入栈,一直到左为空,就可以把栈顶元素放进vector,栈pop,然后给处理栈顶元素的右子树,把右树节点入栈
如果右子树为空,就处理下一次的栈顶,
如果右子树不为空,处理右子树,右子树处理完再栈顶
但是无法分辨这一次处理的是左节点,还是新的右子树,这取决于是否要向左找
所以加了一个flag,如果右为空子树,flag置0,反之置1
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
if (root == nullptr)
return v;
stack<TreeNode*> st;
st.push(root);
int flag = 1;
while (!st.empty())
{
TreeNode* top = st.top();
if (flag && top->left)
{
st.push(top->left);
}
else
{
v.push_back(top->val);
st.pop();
flag = 0;
if (top->right)
{
st.push(top->right);
flag = 1;
}
}
}
return v;
}
};
思路2:
先放左节点入栈,一直到左为空,就可以把栈顶元素放进vector,栈pop,然后给处理栈顶元素的右子树
如果右子树为空,就处理下一次的栈顶的右树,就可以把栈顶元素放进vector,栈pop,
如果右子树不为空,处理右子树,右子树处理完再栈顶的右树
与第一个思路不同的是,第一个,处理的对象是栈顶,这样子把左节点和右子树就混为一谈了,所以需要flag区分
第二个,这里栈只是用来存放左节点,cur是右子树存的位置
vector<int> inorderTraversal2(TreeNode* root)
{
vector<int> v;
if (root == nullptr)
return v;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur || !st.empty())
{
while (cur)
{
st.push(cur);
cur = cur->left;
}
TreeNode* top = st.top();
v.push_back(top->val);
st.pop();
cur = top->right;
}
return v;
}
10. 二叉树的后序遍历 ,非递归迭代实现。
把所有的左节点入栈,栈顶元素左为空,接下开始处理右子树,如果右为空,或者右边已经处理完了,就可以把栈顶元素人vecto和栈pop
如何判断右子树已经处理完了?
标记上一个栈顶,因为右子树的根节点也是右子树的最后一个左节点,也就是上一个栈顶元素。
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> v;
if (root == nullptr)
return v;
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* prev = nullptr;
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;
}