文章目录
- 根据二叉树创建字符串 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉树的层序遍历(分层遍历) 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉树的层序遍历(分层遍历)Ⅱ 🦖
- 🥩 题目描述
- 🥩 解题思路
- 二叉树的最近公共祖先 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉搜索树与双向链表 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 从前序与中序遍历序列构造二叉树 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 从中序遍历与后序遍历序列构造二叉树 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉树的前序遍历(非递归迭代) 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉树的中序遍历(非递归迭代) 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
- 二叉树的后序遍历(非递归迭代) 🦖
- 🥩 题目描述
- 🥩 解题思路
- 🥩 代码
根据二叉树创建字符串 🦖
题目链接
🥩 题目描述
给定一个二叉树节点的 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用来返回每层的vector
while(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; //如果该节点为空节点则返回false
if(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;//空节点即未找到 返回false
path.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;
//说明节点的左右节点都为空需要对该节点进行出栈且返回false
path.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;//返回头节点
}
};
从前序与中序遍历序列构造二叉树 🦖
题目链接
🥩 题目描述
给定两个整数数组preorder
和 inorder
,其中preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点;
示例1:
输入: preorder = [3,9,20,15,7]
, inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
其中该题给出提示:
preorder
与inorder
均无重复元素;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);
}
};
从中序遍历与后序遍历序列构造二叉树 🦖
题目链接
🥩 题目描述
给定两个整数数组 inorder
和 postorder
,其中 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;//返回结果
}
};