【代码随想录】刷题Day14
递归实现的一些理解
1.如果是链表的遍历其实不需要怎么思考;无非就是先定参数然后考虑是先操作后遍历还是先走到底再操作。 包括我之前在写链表的节点删除其实核心思路就是由于链表前面删除后面找不到的原理,以至于我们需要走到链表的底部再进行操作。
2.但是二叉树有两个指针指向左右子树,那么遍历的规则就变了,它有三个遍历模式:前中后序遍历。那么其实前序就当作是链表的先指向后操作;后序也可以当作是先到底后执行操作。
3.那么写递归时最重要的无非就是三个点,把握这三点就能直到递归实现到底如何思考:
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
1.二叉树递归
由于需要返回vector<int>,所以我们传入的参数为引用型参数
//前序遍历
class Solution {
public:
void _preorderR(TreeNode* root,vector<int>& ret)
{
if(root==nullptr)
return;
ret.push_back(root->val);
_preorderR(root->left,ret);
_preorderR(root->right,ret);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
_preorderR(root,ret);
return ret;
}
};
//后序遍历
class Solution {
public:
void _preorderR(TreeNode* root,vector<int>& ret)
{
if(root==nullptr)
return;
_preorderR(root->left,ret);
_preorderR(root->right,ret);
ret.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
_preorderR(root,ret);
return ret;
}
};
//中序遍历
class Solution {
public:
void _preorderR(TreeNode* root,vector<int>& ret)
{
if(root==nullptr)
return;
_preorderR(root->left,ret);
ret.push_back(root->val);
_preorderR(root->right,ret);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
_preorderR(root,ret);
return ret;
}
};
2.非递归遍历
前序遍历的非递归
1.首先我们要知道非递归用栈实现,所以我们保存节点用栈存储,那么大致思路就是遇到根就先进vector,再把这个根入栈,往左走。随后栈顶的节点pop出来往右走
2.如果cur和栈都是空,说明此时循环是结束的,因为我们要插入的节点已经没有了,并且栈中也没有要往右走。
3.在循环里,无非就是再套一个循环,判断条件就是cur是否为空,把cur节点值push_back到vector并且cur节点入栈,随后cur更新为左节点。
4.遍历中间节点和左节点的循环结束,我们此时走到左节点底,因此要遍历走右节点,那么右节点就是将在栈中的节点pop出更新cur为出栈的节点的右节点即可
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
stack<TreeNode*> sT;
TreeNode* cur =root;
while(cur||!sT.empty())
{
while(cur)
{
ret.push_back(cur->val);
sT.push(cur);
cur=cur->left;
}
TreeNode* top = sT.top();
sT.pop();
cur = top->right;
}
return ret;
}
};
中序遍历的非递归
其实和前序非递归写法基本一致,无非就是push_back不一样
1.对于前序遍历,我们是先得到根,所以优先在入栈位置就一定要保存好根节点
2.对于中序遍历不是这样的,我们需要左节点,因此等节点一直判断到左节点的底部,我们才需要将出栈的节点,也就是最左节点push_back到我们所想要的vector中保存即可
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
stack<TreeNode*> sT;
TreeNode* cur =root;
while(cur||!sT.empty())
{
while(cur)
{
sT.push(cur);
cur=cur->left;
}
TreeNode* top = sT.top();
ret.push_back(top->val);
sT.pop();
cur = top->right;
}
return ret;
}
};
后序遍历的非递归
入栈的思路其实差不多,有一些差别的是push_back和判断是否为右节点的操作
1.如果栈顶的没有右节点说明此时这个节点可以出栈
2.如果栈顶的右节点存在但是右节点已经走过了,那么也可以出栈
3.此为的其他条件,都是说明 12 条件不成立,以至于我们需要的往右继续进行遍历入栈
5.那么其实 1和3 都已经知道怎么搞了,唯一还需要判断条件2,我们应该怎么样才能知道节点已经走过右边的子树呢?我们需要一个前驱节点,这个前驱节点是用来判断当前栈顶元素的右边是否为前驱,如果是那么说明走过了,以至于我们可以进行出栈顶操作和bush_back操作
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
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)
{
ret.push_back(top->val);
prev = sT.top();
sT.pop();
}
else
cur=top->right;
}
return ret;
}
};