『 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.mzph.cn/news/236279.shtml

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

相关文章

一篇文章带你了解SpringBoot目录结构

前言 SpringBoot是整合Spring技术栈的一站式框架&#xff0c;是简化Spring技术栈的快速开发脚手架&#xff0c;是一个能够快速构建生产级别的Spring应用的工具。SpringBoot是目前流行的微服务框架&#xff0c;倡导“约定优于配置”&#xff0c;简化Spring项目搭建及开发过程。…

速通Python基础语法--运算符篇

一、算术运算符 优先级&#xff1a; 除法的2个问题&#xff1a; 除零异常&#xff1a; 运行时才出现的错误&#xff0c;叫做“抛出异常” 如果程序运行过程中 抛出异常&#xff0c;程序就会直接终止&#xff0c;后面的代码不会执行。 除法的(不)截断问题&#xff1a; %取模/求…

宝塔面板安装MySQL数据库并通过内网穿透工具实现公网远程访问

文章目录 前言1.Mysql 服务安装2.创建数据库3.安装 cpolar3.2 创建 HTTP 隧道 4.远程连接5.固定 TCP 地址5.1 保留一个固定的公网 TCP 端口地址5.2 配置固定公网 TCP 端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了 Linux 命令行进行繁琐的配置,下面简单几步,通…

微信小程序使用--如何生成二维码

一、生成二维码 1.获取token 参照官方文档说明&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html 其中grant_type是写死的&#xff0c;appid和secret是注册小程序的时候获取的&#xff0c;然后会得到一个默认两小…

MyBatis-Plus全套笔记

一、MyBatis-Plus 1.简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 我们的愿景是成为 MyBatis 最好的搭档&…

c jpeg 理论霍夫曼 DC AC表,c程序实现正向逆向转换

此4张表是理论表&#xff0c;不是针对某张图片的特定表。如编码程序不统计生成某图片的专用霍夫曼表&#xff0c;应该也可用理论表代用编码。 1.亮度DC表 左边第一列是二进制位数&#xff0c;就是对此位数编码 中间一列是生成比特流的位数&#xff0c;右边是生成的比特流。 …

本地MinIO存储服务如何创建Buckets并实现公网访问上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统&#xff0c;它可以100%的运行在标准硬件上&#xff0c;即X86等…

Seata:打造行业首个分布式事务产品

作者&#xff1a;季敏&#xff0c;阿里云分布式事务产品负责人、Seata 开源项目创始人 微服务架构下数据一致性的挑战 微服务开发的痛点 在 2019 年&#xff0c;我们基于 Dubbo Ecosystem Meetup&#xff0c;收集了 2000 多份关于“在微服务架构&#xff0c;哪些核心问题是开…

SparkSQL的编程模型(DataFrame和DataSet)

1.2 SparkSQL的编程模型(DataFrame和DataSet) 1.2.1 编程模型简介 主要通过两种方式操作SparkSQL&#xff0c;一种就是SQL&#xff0c;另一种为DataFrame和Dataset。 SQL SQL不用多说&#xff0c;就和Hive操作一样&#xff0c;但是需要清楚一点的时候&#xff0c;SQL操作的是…

企业需要哪些数字化管理系统?

企业需要哪些数字化管理系统&#xff1f; ✅企业引进管理系统肯定是为了帮助整合和管理大量的数据&#xff0c;从而优化业务流程&#xff0c;提高工作效率和生产力。 ❌但是&#xff0c;如果各个系统之间不互通、无法互相关联数据的话&#xff0c;反而会增加工作量和时间成本…

【递归 回溯】LeetCode-226. 翻转二叉树

226. 翻转二叉树。 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xf…

写给测试同学的福利 | 招募

一、简介 寻找空闲时间有能力进行有偿协助测试的人员&#xff0c;协助大厂产品进行测试优化产品工作 二、要求 1.安卓设备2.具备熟练使用手机的能力 三、你可以得到 1.优先体验相关产品新功能、了解产品走向2.任务有偿&#xff1a;每次任务5-30米 四、需要了解事项 1.嫌…

生于越南,“开源改变了我的人生!”

注&#xff1a;本文精选自《新程序员 007&#xff1a;大模型时代的开发者》&#xff0c;欢迎点击订购。 作者 | 王启隆 责编 | 唐小引 出品 | 《新程序员》编辑部 随着人工智能浪潮的席卷&#xff0c;开源不再仅仅是计算机领域的一个话题&#xff0c;而是成为推动技术创新…

让测试效率起飞的8款浏览器兼容性测试工具,你get了吗?

浏览器的兼容性问题&#xff0c;是指不同浏览器使用内核及所支持的 HTML 等网页语言标准不同&#xff0c;用户客户端的环境不同造成的显示效果不能达到理想效果。 对于用户而言&#xff0c;无论使用哪款浏览器&#xff0c;期望看到的效果是正常的统一的。市面上发布的浏览器版本…

JVS低代码和智能BI(自助式数据分析)12.19更新功能说明

低代码更新功能 新增: 1、表单组件&#xff1a;标题、分割线、按钮等非数据组件增加小程序端隐藏设置&#xff1b; 隐藏设置允许开发者对表单组件中的非数据组件进行隐藏&#xff0c;例如&#xff0c;可能只想展示表单的部分内容&#xff0c;或者希望在特定条件下显示或隐藏…

<JavaEE> 网络编程 -- 网络编程和 Socket 套接字

目录 一、网络编程的概念 1&#xff09;什么是网络编程&#xff1f; 2&#xff09;网络编程中的基本概念 1> 收发端 2> 请求和响应 3> 客户端和服务端 二、Socket套接字 1&#xff09;什么是“套接字”&#xff1f; 2&#xff09;Socket套接字的概念 3&…

整数比较(比较4个数并从小到大输出)C语言xdoj94

描述&#xff1a; 从键盘输入四个整数&#xff0c;要求按由小到大的顺序输出。 输入说明&#xff1a; 输入四个整数&#xff0c;以空格间隔。 输出说明&#xff1a; 输出排序后的整数&#xff0c;以空格间隔。 输入样例 样例1输入 -99 9 99 -9 输出样例 样例1输出 -99 -9 9 99 …

关于“Python”的核心知识点整理大全32

目录 12.6.4 调整飞船的速度 settings.py ship.py alien_invasion.py 12.6.5 限制飞船的活动范围 ship.py 12.6.6 重构 check_events() game_functions.py 12.7 简单回顾 12.7.1 alien_invasion.py 12.7.2 settings.py 12.7.3 game_functions.py 12.7.4 ship.py …

JavaGUI(但期末速成版)之JFrame和JDialog

前言 学到期末发现越来越没时间来细写这些东西了&#xff0c;毕竟蒟蒻博主的发展方向主要需要学的不是Java&#xff0c;但为了期末高分通过&#xff0c;也不得不花一些精力上来&#xff0c;于是有了这样一篇速成GUI&#xff0c;本篇会以十分精简的语言来学习&#xff0c;主打一…

CN3302 PFM升压型双节电池充电控制集成电路外置MOS管 双节锂电池充电IC

CN3302是一款工作于2.7V到6.5V的PFM升压型双节鲤电池充电控制集成电路。CN3302采用恒流和准恒压模式(Quasi-CVTM)对电池进行充电管理&#xff0c;内部集成有基准电压源&#xff0c;电感电流检测单元&#xff0c;电池电压检测电路和片外场效应品体管驱动电路等&#xff0c;具有外…