本文的练习为基础练习,适合刚学习数据结构的小伙伴练习,最好是已经学习过相关的知识再进行练习效果更好。推荐先做练习再看解析哦~
链接:leetcode_144
题目描述:给你二叉树的根节点root
,返回它节点值的前序遍历。
【递归的本质理解】
三序遍历的递归写法的差异,其实是每个节点进入对应函数的次数的差异
以下面这颗二叉树为例,分析其递归的写法
void f(TreeNode* root)
{
if(root == nullptr)
return;
// 第一次进入该节点对应的函数
printf("%d ", root->val);
f(root->left);
// 从左子树递归回来,第二次进入该节点对应的函数
printf("%d ", root->val);
f(root->right);
// 从右子树递归回来,第三次进入该节点对应的函数
printf("%d ", root->val);
}
如果每一次进入函数都打印一遍,那么打印出来的序列将会是下面这样:
1 2 3 3 3 2 2 1 4 5 5 5 4 6 6 6 4 1
而当我们仔细观察就会发现,如果保留第一次进函数的过程,就会输出:
1 2 3 4 5 6
也就是前序遍历的过程
同理,保留第二次和第三次进函数的过程,就输出:
3 2 1 5 4 6
3 2 5 4 6 1
也就是对应中序和后序的过程
所以打印不同次进入函数节点的过程,就是对应不同的遍历方式
根据上面的逻辑,就可以得出前中后序的递归写法了
这里以前序遍历为例,给出相应的代码
class Solution {
public:
vector res;
void preorder(TreeNode* root)
{
if(root == nullptr)
return;
res.push_back(root->val);
preorder(root->left);
preorder(root->right);
}
vector preorderTraversal(TreeNode* root) {
preorder(root);
return res;
}
};
因为栈是后进先出,所以实现先序遍历时进栈要先进右再进左。如果左子树也有左右,那么右子树的节点会一直保留在栈中,等左子树遍历完之后右子树才开始按先序遍历
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
vector res;
if(root)
{
stack st;
st.push(root);
while(!st.empty())
{
TreeNode* cur = st.top();
st.pop();
res.push_back(cur->val);
if(cur->right)
st.push(cur->right);
if(cur->left)
st.push(cur->left);
}
}
return res;
}
};
链接:leetcode_94
与前序遍历类似,只需改变放入res数组和进入左子树的顺序就好了
1)先把左子树干到栈里,直到为空,目的就是每次都是左子树优先打印;
2)然后栈弹出节点,打印此节点,再将右子树重复步骤1),也就是先输出根节点,再把右子树放最后;
3)没子树且栈为空,结束。
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
vector res;
stack st;
if(root == nullptr)
return res;
while(!st.empty() || root != nullptr)
//结束判断条件
{
if(root != nullptr)
{
st.push(root);
root = root->left;
} //对应步骤 1)
else
{
root = st.top();
st.pop();
res.push_back(root->val);
root = root->right;
} //对应步骤 2)
}
return res;
}
};
链接:leetcode_145
与前序遍历类似,只需改变放入res数组的顺序就好了
这个做法简单说就是将前序遍历的 中-左-右 过程换成 中-右-左 过程,再将其逆序成后序
实现方式就是在要输入结果时,改成输入collect栈中,最后再将collect转入res数组中
不过这种做法并不推荐,因为需要额外开一个栈实现,所以空间复杂度是O(n)
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
vector res;
if(root == nullptr)
return res;
stack st;
stack collect;
st.push(root);
while(!st.empty())
{
TreeNode* cur = st.top();
st.pop();
collect.push(cur);
if(cur->left)
st.push(cur->left);
if(cur->right)
st.push(cur->right);
}
while(!collect.empty())
{
res.push_back(collect.top()->val);
collect.pop();
}
return res;
}
};
代码如下(详解见注释):
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
vector res;
if(root == nullptr)
return res;
stack st;
st.push(root);
TreeNode* cur = root;
//没有数据被存入res中,则cur一直为root
//当有数据被处理后,cur就变成对应节点
//每一次cur对应的含义就是上一个入res的值对应的节点
while(!st.empty())
{
root = st.top();
//有左子树且左子树还没被操作(cur对应的是被操作过的节点)
if(root->left != nullptr
&& cur != root->left
&& cur != root->right)
st.push(root->left);
//有右子树且右子树还没被操作,
//因为前面已经考虑过左子树,所以只用判断右子树即可
else if(root->right != nullptr
&& cur != root->right)
st.push(root->right);
else
{
cur = st.top();
res.push_back(st.top()->val);
st.pop();
}
//三个条件判断其实隐含着 左-右-中 的顺序
}
return res;
}
};
链接:leetcode_102
【题目描述】给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
先将root放入队中,然后开始循环:
每次循环为队中的元素个数次(该层元素的个数),在每个元素出队时,
将其左右子树放入队中(为下一层的元素遍历作准备)
class Solution {
public:
vector> levelOrder(TreeNode* root) {
vector> res;
if(!root)
return res;
queue q;
q.push(root);
while(!q.empty())
{
int sz = q.size();
vector ans;
while(sz--)
{
TreeNode* cur = q.front();
q.pop();
ans.push_back(cur->val);
if(cur->left)
q.push(cur->left);
if(cur->right)
q.push(cur->right);
}
res.push_back(ans);
}
return res;
}
};
链接:leetcode_103
这题其实就是上一题的延伸,只需要加一个条件判断隔行逆置一下就可以了
class Solution {
public:
vector> zigzagLevelOrder(TreeNode* root) {
vector> res;
if(!root)
return res;
queue q;
q.push(root);
while(!q.empty())
{
int sz = q.size();
vector ans;
while(sz--)
{
TreeNode* cur = q.front();
q.pop();
ans.push_back(cur->val);
if(cur->left)
q.push(cur->left);
if(cur->right)
q.push(cur->right);
}
if(res.size() % 2 == 1) reverse(ans.begin(), ans.end());
res.push_back(ans);
}
return res;
}
};
你学会了吗?