『 C++ 』二叉树进阶OJ题

文章目录

    • 根据二叉树创建字符串 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉树的层序遍历(分层遍历) 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉树的层序遍历(分层遍历)Ⅱ 🦖
      • 🥩 题目描述
      • 🥩 解题思路
    • 二叉树的最近公共祖先 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉搜索树与双向链表 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 从前序与中序遍历序列构造二叉树 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 从中序遍历与后序遍历序列构造二叉树 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉树的前序遍历(非递归迭代) 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉树的中序遍历(非递归迭代) 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码
    • 二叉树的后序遍历(非递归迭代) 🦖
      • 🥩 题目描述
      • 🥩 解题思路
      • 🥩 代码


根据二叉树创建字符串 🦖

题目链接请添加图片描述

🥩 题目描述

给定一个二叉树节点的 root ,采用前序遍历的方式将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串

空节点使用一对空括号 () 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对;

示例1:

输入: root = [ 1 , 2 , 3 , 4 ]

输出: 1 ( 2 ( 4 ) ) ( 3 )

解释: 初步转化后得到 1 ( 2 (4) () () ) ( 3 () () );

由题目可知,需要得到前序遍历的结果,即",左子树,右子树";

且示例中得到当左右子树为空时则不需要括号,左子树为空右子树不为空时左子树需要括号,右子树为空时括号省略;


🥩 解题思路

该题的解题思路即为采用分治的思路,将问题按照前序遍历的方式(“,左子树,右子树”)化为对应的结果;

返回值为string,所以当返回结果为数字时可以使用to_string()函数将数字结果转化为string返回;

根据前序遍历结果初步操作可以为:

class Solution {
public:string tree2str(TreeNode* root) {if(root == nullptr) return"";//当节点为空时不予操作返回空字符串return to_string(root->val) + "(" + tree2str(root->left) + ")" + "(" + tree2str(root->right) + ")"//问题转化为子问题即采用前序遍历的方式对其进行处理}

以示例1为例这里运行的结果为1 ( 2 (4) () () ) ( 3 () () );

接下来进行特殊处理:

当节点左右都为空时只需要返回节点本身的val;

当节点左为空右不为空时默认打印出左节点val及左节点的();

右节点为空左节点不为空时只打印左节点val以及所对应的();


🥩 代码

class Solution {
public:string tree2str(TreeNode* root) {if(root == nullptr) return"";//节点为空返回空字符串if(root->left == root->right && root->left == nullptr){return to_string(root->val);//左右子树都为空只返回该节点的val值}if(root->right == nullptr){return to_string(root->val) + "(" + tree2str(root->left) + ")";//右节点为空但左节点不为空时不打印右节点对应的()//左节点为空但右节点不为空时默认打印即可}return to_string(root->val) + "(" + tree2str(root->left) + ")" + "(" + tree2str(root->right) + ")";}
};

二叉树的层序遍历(分层遍历) 🦖

题目链接

🥩 题目描述

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例1:

输入 :root = [3,9,20,null,null,15,7];
输出 :[[3],[9,20],[15,7]];

由题可知,该题需要进行层序遍历,且使用C++解答时需要返回对应的二维数组;


🥩 解题思路

该题的解题思路与层序遍历如出一辙,可以使用queue容器对应的将节点进行保存;

且当访问一个根节点就代入对应的左右节点;

当然在入左右节点时需要判断节点是否为空,避免在对节点进行访问的时候出现非法的空指针引用;


🥩 代码

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {//返回一个vector<vector<int>> ,vector<vector<int>> ret;if(root == nullptr) return ret;//若是节点为空则直接返回一个空的vector<vector<int>>queue<TreeNode*> qLeve;//创建一个队列(LILO容器)保证能将数据节点按照顺序进行访问,且按照顺序进行父节点出子节点入的方式进行;qLeve.push(root);//为了保证下面的循环条件,先将根节点入队列while(!qLeve.empty()){//当根节点不为空时进行循环size_t leveSize = qLeve.size();//使用队列的queue::size()属性判断这次所入队列为多少需要进行几层判断//如第一层的节点只有一个节点(根节点),所以该层的数据只有一个vector<int> tmpV;//创建临时的vector用来返回每层的vectorwhile(leveSize--){tmpV.push_back(qLeve.front()->val);//在vector中插入对应的数据//当一个节点出队时需要入其对应的左右子树if(qLeve.front()->left) qLeve.push(qLeve.front()->left) ;if(qLeve.front()->right) qLeve.push(qLeve.front()->right) ;qLeve.pop();//出节点}ret.push_back(tmpV);//将临时的vector放入至需要返回的vector<vector<int>>容器中}return ret;//返回二维数组}
};

二叉树的层序遍历(分层遍历)Ⅱ 🦖

题目链接

🥩 题目描述

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历);

输入 :root = [3,9,20,null,null,15,7];
输出 :[[15,7],[9,20],[3]];


🥩 解题思路

解题思路即为上题代码,当最终的vector<vector<int>>成型之后,将该容器对象使用reverse()函数进行逆置;

代码不再进行赘述;


二叉树的最近公共祖先 🦖

题目链接

🥩 题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

需要注意在这题当中题目描述给出注意事项:

两个节点必定存在于该树中;

且两棵树的节点树 left.size() == right.size();

示例1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例2:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身;

从题目描述可知对应的最近公共祖先的概念;


🥩 解题思路

思路1(暴力解法):

思路1的方法及为创建一个子函数,根据子函数来判断两个节点是否存在于该节点的左子树于右子树;

当节点存在于该节点的左子树与右子树则代表该节点即为两个节点的最近公共祖先;


思路2(DFS):

思路2的方式即为一样采取子函数的方式,利用两个栈LIFO的特性来获取两个节点对应的路径;

首先需要确保两条路径的长度相同,及长度较长的部分必定不为最近公共祖先,将长度较长的栈容器将元素pop()至两个容器的长度相等;

再根据容器的路径判断其中最先相同的最近公共祖先;


思路3(DFS):

思路3的方式与思路2 的方式相当,但是不需要子函数;

思路即为采用递归的方式判断左右两个子树,当节点访问至两个节点的其中一个节点时返回该节点;

遇到空nullptr时返回空指针;

当节点左子树为空时返回右子树的结果(表示最近公共祖先不存在与该节点与该节点的左子树);

当节点右子树为空时返回左子树的结果(表示最近公共祖先不存在与该节点与该节点的右子树);

当两者都不为空时则表示该节点即为两个节点的最近公共祖先;


🥩 代码

思路1(暴力解法):

class Solution {
public:bool IsinLeft(TreeNode*tofind, TreeNode*cur){if(cur == nullptr) return false; //如果该节点为空节点则返回falseif(cur->val == tofind->val) return true;//如果该节点即为所寻找的节点返回true//该处操作则表示该节点不为最近公共祖先,需要向下继续遍历bool tmpleft = IsinLeft(tofind, cur->left) ;if(tmpleft) return true;//如果该节点左子树的结果为真则表示存在于该节点的左子树bool tmpright = IsinLeft(tofind,cur->right);if(tmpright) return true;//如果该节点右子树的结果为真则表示存在于该节点的右子树return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//思路为递归思路,且需要一个子函数用于判断两个节点的位置if(root == nullptr) return nullptr;if( root == p || root == q ) {//如果两个节点其中一个节点为root节点那么说明root节点结尾最近的公共祖先return root;}//判断p节点于q节点是否存在于左右子树当中bool pInleft = IsinLeft(p,root->left);bool pInright = !pInleft;bool qInleft  = IsinLeft(q,root->left);bool qInright  = !qInleft;if(( pInleft && qInright ) || ( qInleft && pInright )){//这个判断表示这个节点即为最近的公共祖先return root;}//如果两个节点都在该树的左子树当中则不再去右子树遍历,应遍历其左子树,反则遍历其右子树if(pInleft && qInleft) return lowestCommonAncestor(root->left,p,q);else return lowestCommonAncestor(root->right,p,q);}
};

思路1的题解思路较好理解,但是其代码过于复杂,时间复杂度过高O(N^2),即极端情况下需要遍历所有节点N,且所有节点都需要再次进行遍历(递归)N;


思路2:

class Solution {
public://思路2 :使用两个栈来存放路径 寻找两个节点的路径判断路径中最近公共祖先bool GetPath(TreeNode* root,TreeNode*tofind,stack<TreeNode*> &path){if(root == nullptr) return false;//空节点即未找到 返回falsepath.push(root);//先对节点进行插入再进行判断if(path.top()->val == tofind->val){return true;}if(GetPath(root->left,tofind,path)) return true;if(GetPath(root->right,tofind,path)) return true;//说明节点的左右节点都为空需要对该节点进行出栈且返回falsepath.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == p || root == q) return root;stack<TreeNode*> pPath;stack<TreeNode*> qPath;GetPath(root,p,pPath);//调用子函数使得每个栈都能获取对应的路径GetPath(root,q,qPath);//路径获取完毕之后对路径进行处理while(pPath.size()!=qPath.size()){if(pPath.size() > qPath.size())//但凡其中一个size大都表示不为公共祖先需要出栈pPath.pop();if(pPath.size() < qPath.size())qPath.pop();}//此处两个路径的大小已经相同,判断两个路径中的最近公共祖先while(pPath.top()!=qPath.top()){qPath.pop();pPath.pop();}return pPath.top();}
};

该方法在时间发杂度中优于思路1的暴力解法;


思路3:

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == nullptr) return nullptr;//如果root节点为空时直接返回避免对空指针非法解引用if(root == p || root == q) return root;//当root 为p或是q时说明这个节点为最近的公共祖先;//使用两个指针来保存遍历的结果TreeNode* left = lowestCommonAncestor(root->left, p,q);TreeNode* right = lowestCommonAncestor(root->right, p,q);//当左为空时表示左子树不存在其中一个节点返回右子树结果if(left == nullptr)return right;//当右为空时表示右子树不存在其中一个节点返回左子树结果if(right == nullptr)return left;//当两个指针的返回结果都不为空时即表示该节点为两个节点的最近公共祖先return root;}
};

二叉搜索树与双向链表 🦖

题目链接请添加图片描述

🥩 题目描述

输入一棵二叉树,将二叉树转换成一个排序的双向链表;

如下图所示:

数据范围 : 输入二叉树的节点数 0 ≤ n ≤ 10000 ≤ n ≤ 1000,二叉树中每个节点的值 0 ≤ val ≤ 10000 ≤ val ≤ 1000
要求:空间复杂度O ( 1 )(即在原树上操作),时间复杂度 O ( n )

注意:

1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

4.你不用输出双向链表,程序会根据你的返回值自动打印输出

输入描述 : 二叉树的根节点

返回值描述 : 双向链表的其中一个头节点;

示例1:

输入 : {10,6,14,4,8,12,16}

返回值 : From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;

说明 : 输入题面图中二叉树,输出的时候将双向链表的头节点返回即可;


🥩 解题思路

思路1:

该题若是没有空间复杂度限制的情况可以采用另一容器vector<TreeNode>对其中的节点利用中序遍历进行重新排布;

再遍历vector对象以双指针前后指针的方式重新将节点进行排布即可;


思路2:

思路2的方式与思路1的方式类似,即在原树当中采用类似前后指针的方式对该树进行访问;

创建一个子函数;

利用递归的方式一个指针记录前一个节点,一个指针记录后一个节点实现在原树中重排;


🥩 代码

思路1:

/*
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 , vector<TreeNode*> &V){//存在搜索二叉树,利用中序遍历将其节点按照顺序进行保存if(root == nullptr) return;InOrder(root->left, V);V.push_back(root);InOrder(root->right, V);}TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree == nullptr) return nullptr;vector<TreeNode*> v;InOrder(pRootOfTree, v);for(int Vleft = 0,Vright = Vleft+1;Vright<v.size();Vleft++,Vright++){v[Vleft]->right = v[Vright];v[Vright]->left = v[Vleft];}TreeNode* cur = v[0];while(cur){cout<<cur->val<<" ";cur = cur->right;}return v[0];}
};

该方法并不符合题意,但在OJ题中可以通过;


思路2:

/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/
class Solution {
public://题目要求原地算法要求空间复杂度为O(1) void _Convert(TreeNode*& prev , TreeNode*cur){if(cur == nullptr) return ;//当节点为空时不做处理//采用中序遍历的方式进行遍历_Convert(prev , cur->left);cur->left = prev;//由于参数为*&指针引用,即该指针即为上一个指针的节点//可以采用上一个指针的节点的right来指向该指针实现双向//判断prev是否为空避免对空指针的非法解引用if(prev) prev->right = cur;prev = cur;_Convert(prev , cur->right);}TreeNode* Convert(TreeNode* pRootOfTree) {TreeNode* prev = nullptr;_Convert(prev,pRootOfTree);//根据该节点找到链表的头节点从而返回对应的链表头节点TreeNode* head = pRootOfTree;while(head&&head->left){head = head->left;}return head;//返回头节点}
};

从前序与中序遍历序列构造二叉树 🦖

题目链接请添加图片描述

🥩 题目描述

给定两个整数数组preorderinorder ,其中preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点;

示例1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

其中该题给出提示:

  • preorderinorder无重复元素;
  • inorder 均出现preorder中;
  • preorder 保证为二叉树的前序遍历序列;
  • inorder 保证为二叉树中的中序遍历序列;

🥩 解题思路

该题的思路即为类似快速排序思路使用前序遍历确定树与各个子树的根节点并利用中序遍历判断左右区间,分别分为[ inbegin , mid-1 ] mid [ mid+1 , inend ]的方式对中序遍历进行分治;

当节点为根节点的时候可以直接创建节点;

创建节点之后由于该节点为每次递归的根节点所以需要根据中序遍历来判断中间节点位置并根据中间节点区分出左右子区间;

当区分出左右子区间时则可以继续递归(按照前序遍历);

最后应该注意:

由于该题思路为区间思路,所以可能出现区间不存在的可能,即当区间不存在时则不能再继续向下访问应该及时返回空指针;


🥩 代码

/*** 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* _buildTree(vector<int>& preorder, vector<int>& inorder,int& cur,int inbegin,int inend){//利用子函数来进行递归,该思路类似于快速排序,将问题以分治的思路划分为子问题;/* * 其中两个vector为题目所给分别为前序与中序遍历序列* cur用来遍历前序遍历序列确定每棵子树的根节点位置* inbegin 与 inend 来区分中序遍历序列中的左右子区间*/if(inbegin>inend) return nullptr;//最后添加:由于为区间分布,所以可能出现区间不存在的可能,当区间不存在时则不能再向下遍历应当返回空指针TreeNode* newnode = new TreeNode(preorder[cur]);//利用前序遍历序列创建节点int mid = inbegin;while(mid<=inend){ //循环条件:只剩最后一个节点的时候也仍需进行分治思路进行递归if(inorder[mid] == preorder[cur]) break;    //判断mid所在位置在中序遍历中的位置从而进行区分中序遍历中的左右子区间++mid;}++cur;//将cur指针自增用于遍历下一个前序遍历中的节点//分别递归,当返回时节点被链接newnode->left = _buildTree(preorder,inorder,cur,inbegin,mid-1);newnode->right = _buildTree(preorder,inorder,cur,mid+1,inend);return newnode;//返回节点}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int cur = 0;//子函数中的cur为引用,不能直接传参常量return _buildTree(preorder,inorder,cur,0,preorder.size()-1);}
};

从中序遍历与后序遍历序列构造二叉树 🦖

题目链接请添加图片描述

🥩 题目描述

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这棵二叉树

示例1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3];
输出:[3,9,20,null,null,15,7];

从题目描述中可以看出该题与上一题的思路如出一辙;


🥩 解题思路

该题的思路与[从前序与中序遍历序列构造二叉树]十分相似,唯一不同的是一个给的是前序遍历序列,一个给的是后序遍历序列;

其思路也大致相同,即采用中序遍历序列来判断左右子区间,用后序遍历序列来判断根节点;


🥩 代码

class Solution {
public:TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder,int& cur,int inbegin,int inend){if(inbegin>inend) return nullptr;TreeNode* newnode = new TreeNode(postorder[cur]);int mid = inbegin;while(mid<=inend){ if(inorder[mid] == postorder[cur]) break;    ++mid;}--cur;newnode->right = _buildTree(inorder,postorder,cur,mid+1,inend);newnode->left = _buildTree(inorder,postorder,cur,inbegin,mid-1);return newnode;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int cur = postorder.size() - 1;return _buildTree(inorder, postorder, cur, 0, inorder.size() - 1);}
};

二叉树的前序遍历(非递归迭代) 🦖

题目链接请添加图片描述

🥩 题目描述

给定一个二叉树的根节点root , 返回它节点值的 前序 遍历;

示例1:

输入 : root = [ 1 , null , 2 , 3 ];

输出:[ 1 , 2 , 3 ];


🥩 解题思路

利用C++完成这道题时需要返回一个vector<int>;

该题以递归的思路来做的话会较为简单,根据每次递归子树时将会遇到的情况做特殊处理并将其放到一个vector<int>容器当中并返回;

而若是非递归的话则不能使用递归的这种思路;

但是使用非递归的话可以利用其他的容器对树中的节点做处理;

就以该题前序遍历为例;

前序遍历的路径(子树访问的顺序)为根,左子树,右子树;

这里不以示例1为例,以该图为例;

这棵树以前序遍历最终得到的结果为[ 10 , 6 , 4 , 8 , 14 , 12 , 16 ] ;

即其可以看成左路节点左路节点右子树的左路节点,将问题化为子问题;

以该图为例即为将一棵树分为左路节点以及左路节点的右子树;

将问题划分为子问题;

可以使用一个stack容器(栈),根据其LIFO的特性将数据进行存储,并且反向拿出,即以该树的左路节点为例,将左路节点全部入栈,当栈顶左路节点被出时访问它的右子树;

该种方式不是递归但是思路上胜似递归;


🥩 代码

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;//用来存放左路节点vector<int> ret;//用来做返回TreeNode *cur = root;while(!st.empty() || cur){while(cur){//节点必定存在,要么在栈中要么为cur所在位置//故当栈不为空或者cur不为nullptr时将其入队列st.push(cur);//由于该遍历方式为前序遍历即 (根,左子树,右子树) 故该节点可以直接访问ret.push_back(cur->val);cur = cur->left;//遍历左路节点}//每次去访问栈顶元素(左路节点)的右子树TreeNode*top = st.top();st.pop();cur = top->right;}return ret;}
};

二叉树的中序遍历(非递归迭代) 🦖

题目链接请添加图片描述

🥩 题目描述

给定一个二叉树的根节点 root ,返回它的中序遍历;

示例1:

输入 : root = [ 1 , null , 2 , 3 ];

输出:[ 1 , 3 , 2 ];


🥩 解题思路

该题的解题思路与上一题二叉树的中序遍历如出一辙,即也是通过将树分为左路节点与左路节点的右子树的方式;

但稍微不同的是,对于该题来说由于是以中序遍历即(左子树,根,右子树)的方式对树进行遍历,所以第一次对左路节点进行遍历时不能直接进行访问,当左子树访问完后才能访问这个子树的根节点(左路节点);

大体上还是相同;


🥩 代码

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> ret;//作为函数返回stack<TreeNode*>st;//用来访问左路节点及节点的右子树TreeNode*cur = root;//该指针用来遍历左路节点while(cur || !st.empty()){//节点必定存在,要么在当前cur,要么在栈中,否则即为遍历结束while(cur){st.push(cur);//将节点入栈,且在入栈时不能直接进行访问,当该节点的左子树被访问完毕才能访问该节点cur = cur->left;}TreeNode* top = st.top();st.pop();//这个节点必定是这棵树(子树)的左路节点,根据中序遍历(左子树 根 右子树)的顺序可以访问该节点ret.push_back(top->val);//访问该左路节点的右子树cur = top->right;}return ret;}
};

二叉树的后序遍历(非递归迭代) 🦖

题目链接请添加图片描述

🥩 题目描述

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

示例1:

输入:root = [1,null,2,3]
输出:[3,2,1]


🥩 解题思路

思路1:

思路1的思路即为将前序遍历进行修改,将其修改为==(根,右子树,左子树)的访问顺序==,再最后对结果进行一次逆置,即:后序遍历(左子树,右子树,根);

这种方式的思路是可行的,但是若是需要边遍历边进行打印则不可取;

该种方法本质上是一种取巧的办法,但是在该题中可以运行;


思路2:

以类似于前序与中序遍历的思路相同,但是唯一不同的是在前序遍历或者中序遍历时,前序遍历时根节点可以直接访问,而中序遍历时节点的左子树访问完毕后即可以访问根节点;

而后序遍历与前序中序不同的是后序遍历必须将节点的左右子树都访问结束后才能访问根节点;

由于也是按照左路节点与左路节点的右子树的左路节点将问题化为子问题进行迭代的思路,所以左路节点的左子树可以默认为已经访问完毕,此时只需要处理节点的右子树即可;

当节点的右子树为nullptr时为了避免对空指针的非法解引用操作,所以当该节点的右子树为空时空间直接访问该节点;

当该节点的右子树访问完毕时也可以访问该节点,那么问题是如何判断该节点的右子树是否被访问完毕?

可以使用一个prev指针来记录每次所访问节点的位置,并将该位置记住,当在栈顶出取出节点时,如果该节点的右子树为空或者是该节点的右子树等于上次访问过的节点(即prev)时表示该节点的右子树已经被访问,可以直接访问该节点;


🥩 代码

思路1:

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;//用来存放右路节点vector<int> ret;//用来作返回TreeNode *cur = root;//用于遍历while(!st.empty() || cur){while(cur){//节点必定存在,要么在栈中要么为cur所在位置//故当栈不为空或者cur不为nullptr时将其入队列st.push(cur);//由于该遍历方式为变相的前序遍历 即 (根,右子树,左子树) 故该节点可以直接访问ret.push_back(cur->val);cur = cur->right;//遍历右路节点}//每次去访问栈顶元素(右路节点)的左子树TreeNode*top = st.top();st.pop();cur = top->left;}//得出的结果即为 (根,右子树,左子树),逆置后即为 (左子树,右子树,根)即为后序遍历顺序;reverse(ret.begin(),ret.end());return ret;}
};

该思路为前序遍历的修改并采用逆置得到后序遍历的结果;


思路2:

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;//用来存放左路节点vector<int> ret;//用来做返回TreeNode *cur = root;//遍历左路节点TreeNode*prev = nullptr; //用来记录每个被访问的节点while(!st.empty() || cur){while(cur){//遍历左路节点,这里遍历左路节点时只对左路节点进行入栈操作//由于是后序遍历所以第一次访问根节点的时候不能进行访问st.push(cur);cur = cur->left;}TreeNode*top = st.top();//取出栈顶节点并准备访问栈顶节点的右子树(左子树默认已经访问完毕)if(top->right == nullptr || prev == top->right){//当右子树为空时为了避免对空指针的非法解引用且没必要再对右子树进行访问//由于prev指针记录了每次上一次访问的节点,所以当prev == 该节点的右子树时则表示该节点的右子树已经被访问完毕//可以直接访问该节点ret.push_back(top->val);st.pop();prev = top;}else{//表示该节点的右子树未被访问过,需要先访问该节点的右子树cur = top->right;}}return ret;//返回结果}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/222655.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

llvm后端之DAG设计

llvm后端之DAG设计 引言1 核心类设计2 类型系统2.1 MVT::SimpleValueType2.2 MVT2.3 EVT 3 节点类型 引言 llvm后端将中端的IR转为有向无环图&#xff0c;即DAG。如下图&#xff1a; 图中黑色箭头为数据依赖&#xff1b;蓝色线和红色线为控制依赖。蓝色表示指令序列化时两个节…

【Unity URP渲染管线下GUI圆形扇形_多功能线框Shader_案例分享】

【Unity URP渲染管线下GUI圆形&扇形_多功能线框Shader_案例分享】 上图看效果

搭建Eureka服务

搭建Eureka服务 文章目录 搭建Eureka服务搭建EurekaServer注册user-service注册多个实例 在order-service中完成服务拉取和负载均衡 搭建EurekaServer <dependency><!--eureka服务器--><groupId>org.springframework.cloud</groupId><artifactId>…

自我学习--关于如何设计光耦电路

本人在项目中多次设计光耦电路&#xff0c;目前电路在项目中运行比较平稳&#xff0c;所以总结一下自己的设计经验&#xff0c;与大家交流一下&#xff0c;如有错误还希望大家指出改正&#xff0c;谢谢&#xff08;V&#xff1a;Smt15921588263&#xff1b;愿与大家多交流&…

机器学习--线性回归

目录 监督学习算法 线性回归 损失函数 梯度下降 目标函数 更新参数 批量梯度下降 随机梯度下降 小批量梯度下降法 数据预处理 特征标准化 正弦函数特征 多项式特征的函数 数据预处理步骤 线性回归代码实现 初始化步骤 实现梯度下降优化模块 损失与预测模块 …

JVM启动流程(JDK8)

JVM启动流程(JDK8) JVM的启动入口是位于jdk/src/share/bin/java.c的JLI_Launch函数,其定义如下: int JLI_Launch(int argc, char ** argv, /* main argc, argc */int jargc, const char** jargv, /* java args */int appclassc, const char** appclass…

【2023年网络安全优秀创新成果大赛专刊】银行数据安全解决方案(天空卫士)

在2023年网络安全优秀创新成果大赛&#xff0c;成都分站中&#xff0c;天空卫士银行数据安全方案获得优秀解决方案奖。与此同时&#xff0c;天空卫士受信息安全杂志邀请&#xff0c;编写《银行数据安全解决方案》。12月6日&#xff0c;天空卫士编写的《银行数据安全解决方案》做…

SpringIOC之MethodBasedEvaluationContext

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

逆波兰计算器的完整代码

前置知识&#xff1a; 将中缀表达式转为List方法&#xff1a; //将一个中缀表达式转成中缀表达式的List//即&#xff1a;(3042)*5-6 》[(, 30, , 42, ), *, 5, -, 6]public static List<String> toIndixExpressionList(String s) {//定义一个List&#xff0c;存放中缀表达…

[python]python实现对jenkins 的任务触发

目录 关键词平台说明背景一、安装 python-jenkins 库二、code三、运行 Python 脚本四、注意事项 关键词 python、excel、DBC、jenkins 平台说明 项目Valuepython版本3.6 背景 用python实现对jenkins 的任务触发。 一、安装 python-jenkins 库 pip install python-jenkin…

文件的使用(初阶)

前言&#xff1a; 我们先来看一个内存使用图&#xff1a; 这其中内核空间用户代码是不能读写的专门留给操作系统内核去使用。 但是这一篇我们来讲文件&#xff08;上方内存图意义不明&#xff0c;哈哈&#xff0c;权当复习&#xff09;。 文件是用来存放数据的&#xff0c;但是…

配置BGP的基本示例

BGP简介 定义 边界网关协议BGP&#xff08;Border Gateway Protocol&#xff09;是一种实现自治系统AS&#xff08;Autonomous System&#xff09;之间的路由可达&#xff0c;并选择最佳路由的距离矢量路由协议。早期发布的三个版本分别是BGP-1&#xff08;RFC1105&#xff0…

核货宝订单管理系统提高企业效率

核货宝订单管理系统可以帮助企业提高效率&#xff0c;具体体现在以下几个方面&#xff1a; 一、订单自动化处理&#xff1a;核货宝订单管理系统支持订单批发和多渠道订单导入&#xff0c;它可以从订单的接收、处理、跟进、发货、到售后服务等环节都可以通过系统自动完成&#x…

再看参数校验

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 写一个接口&#xff0c…

OpenFeign 万字教程详解

OpenFeign 万字教程详解 目录 一、概述 1.1.OpenFeign是什么&#xff1f;1.2.OpenFeign能干什么1.3.OpenFeign和Feign的区别1.4.FeignClient 二、OpenFeign使用 2.1.OpenFeign 常规远程调用2.2.OpenFeign 微服务使用步骤2.3.OpenFeign 超时控制2.4.OpenFeign 日志打印2.5.O…

数据可视化:赋能企业决策的视觉力量

数据可视化在企业中扮演着至关重要的角色&#xff0c;为决策者提供了直观、深入的数据解读&#xff0c;帮助他们更好地理解业务状况并作出明智的决策。今天我就以可视化从业者的角度来简谈说说如何让数据可视化为更好地为企业服务。 首先&#xff0c;数据可视化可以让数据更易…

HFish蜜罐搭建及简单使用

一、HFish蜜罐 HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知和威胁情报领域的…

类和对象(下篇)

再谈构造函数 构造函数体赋值 在之前的学习中我们知道&#xff0c;在创建一个对象时&#xff0c;我们的编译器就会自动调用构造函数将对象初始化&#xff0c;给对象中各个成员变量一个合适的初始值。 例如&#xff1a; class Date { public:Date(int year, int month, int d…

【pentaho】kettle读取Hive表不支持bigint和timstamp类型解决。

一、bigint类型 报错: Unable to get value BigNumber(16) from database resultset显示kettle认为此应该是decimal类型(kettle中是TYPE_BIGNUMBER或称BigNumber)&#xff0c;但实际hive数据库中是big类型。 修改kettle源码解决&#xff1a; kettle中java.sql.Types到kettle…

创建Github Pages 仓库

Github Pages 仓库创建 1. 在 GitHub 上创建一个新仓库2. 在仓库中创建一个分支&#xff08;可选&#xff0c;可跳过&#xff09;3. 创建您的静态网站4. 启用 GitHub Pages5. 等待构建完成6. 访问您的网站 在 GitHub 上创建一个 GitHub Pages 仓库是相对简单的。GitHub Pages 允…