『 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用来返回每层的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;//返回头节点
		
    }
};

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

题目链接请添加图片描述

🥩 题目描述

给定两个整数数组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.mfbz.cn/a/260749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp websocket的使用和封装

在uniapp中socket分为两种形式&#xff0c;第一种适用于只有一个socket链接&#xff0c;第二种适用于多个socket链接。传送门 这里以socketTask为列子封装 在utils新建一个文件 在你要使用的页面引入&#xff0c;我这是聊天那种&#xff0c;所以我在拿到用户信息之后连接sock…

【STM32单片机入门-1】堆栈/全局变量,局部变量,静态全局变量,局部静态变量等

1&#xff0c;堆栈对比 堆&#xff1a;由程序员分配和释放。容易产生碎片&#xff0c;使用方便&#xff0c;地址分配使用从下到上 栈&#xff1a;用来存放函数地址和局部参数&#xff0c;主函数使用时&#xff0c;要对函数的首地址断点保存&#xff0c;地址分配从上到下&#…

微软官方发布的C#开源、免费、实用的Windows工具箱

前言 今天分享一款由微软官方发布的C#开源、免费、实用的Windows工具箱&#xff08;帮助用户调整和简化Windows系统的体验&#xff0c;从而提高工作效率&#xff09;&#xff1a;Microsoft PowerToys。 项目介绍 Microsoft PowerToys 是使用 C 和 C# 编程语言开发的。它利用了 …

ansible的playbook

1、playbook的组成部分 &#xff08;1&#xff09;task任务&#xff1a;在目标主机上执行的操作&#xff0c;使用模块定义这些操作&#xff0c;每个任务都是一个模块的调用 &#xff08;2&#xff09;variables变量&#xff1a;存储和传递数据&#xff08;变量可以自定义&…

DRF从入门到精通二(Request源码分析、DRF之序列化组件)

文章目录 一、Request对象源码分析区分原生request和新生request新的request还能像原来的reqeust一样使用吗源码片段分析总结&#xff1a; 二、DRF之序列化组件序列化介绍序列化步骤序列化组件的基本使用反序列化基本使用反序列化的新增反序列化的新增删除单条 反序列化的校验 …

天猫数据分析(天猫查数据工具):2023年天猫平台假发行业市场销售数据分析报告

如今&#xff0c;由于人们工作和生活的压力较大&#xff0c;居民脱发问题严重&#xff0c;且脱发群体倾向于80后和90后&#xff0c;逐渐向低龄化发展。除脱发外&#xff0c;在颜值经济的背景下&#xff0c;人们越来越注重外貌和形象&#xff0c;假发作为一种改善发型的工具&…

Graylog配置日志保留策略

找了半天没找到说的清楚的&#xff0c;只能抠官方文档 graylog的归档&#xff08;日志持久化&#xff09;只有付费版才能用&#xff0c;所以日志只能存在es中 1.理解官方给出的几个概念 轮转策略 (Index Rotation Strategy): 轮转策略定义了何时创建新的索引以及何时关闭旧的索…

ssm基于vue技术的绿色蔬菜销售管理系统+vue论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本绿色蔬菜销售管理就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…

网络技术基础与计算思维实验教程_2.2_单交换机实验_重制版

实验内容 实验目的 实验原理 关键命令说明 开始实验 构建 选择交换机 选择终端--台式机 放置四台终端 直通线连接 依次连接pc0到pc3 终端配置Ip地址和子网掩码 完成了交换机和终端连接以后,为每一个终端配置Ip地址和子网掩码 单击pc0 在选择桌面选项卡中选择Ip配置使用程序 …

快速从图中提取曲线坐标数据的在线工具WebPlotDigitizer

快速从图中提取曲线坐标数据的在线工具WebPlotDigitizer 1 介绍2 WebPlotDigitizer在线版的使用2.1 上传图像2.2 点击横纵坐标点&#xff1a;2.3 选择曲线 3 查看数据参考 1 介绍 写论文时要对比别人曲线图、点图、柱形图的数据&#xff0c;但是只有图没有原始数据怎么办&…

【51单片机系列】C51中的中断系统扩展实验

本文是关于51单片机中断系统的扩展实验。 文章目录 一、 扩展实验一&#xff1a;使用外部中断0控制蜂鸣器&#xff0c;外部中断1控制直流电机二、扩展实验二&#xff1a;修改定时器初值&#xff0c;设定3秒钟的定时时间让LED模块闪烁三、扩展实验三&#xff1a;使用定时器1和数…

基于NestJS 和 TypeORM 实现 CURD RESTful API接口

前言 对于服务端项目而言&#xff0c;对外如何提供合格规范的HTTP接口&#xff0c;对内如何优雅的操作数据存储&#xff0c;比如mysql、mongodb。 本文是NestJS服务端开发的基础入门教程&#xff0c;我会根据成熟的解决方案&#xff0c;给大家详细介绍如何基于NestJS实现开发…

【RTOS学习】源码分析(信号量和互斥量 事件组 任务通知)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f353;信号量和互斥量&#x1f345;创建&#x1f345;Take&#x1f345;Give &#x…

中国激光雷达的2023:倔强的笨小孩

作者 |David 编辑 |王博 现在回头来看&#xff0c;从2007年莱万多夫斯基和大卫霍尔在硅谷骑着摩托车四处兜售激光雷达开始&#xff0c;到2023年仅中国车载市场出货量接近60万&#xff0c;覆盖了市面上40%以上搭载高阶智驾的新车型&#xff0c;激光雷达一直在用有力的数据回应着…

华为atlas300安装教程

1、安装包位置&#xff1a; /data/ai_install_packages 2、添加HwHiAiUser用户&#xff1a; groupadd -g 1000 HwHiAiUser useradd -g HwHiAiUser -u 1000 -d /home/HwHiAiUser -m HwHiAiUser -s /bin/bash 3、安装驱动&#xff1a; ./Ascend-hdk-310p-npu-driver_6.0.0_l…

HashMap扩容是2倍的原因(全网博客几乎都解释错了)

零、前言 最近在写博客时&#xff0c;突然又想起来哪个经常出现在面试题里的问题&#xff1a; HashMap扩容为什么是原来的2倍&#xff1f; 因为看过源码&#xff0c;我觉得这个问题并不难。在我之前的通俗解释equals和hashCode的关系和作用里也说过这个原因。但为了博客的严谨…

DesignDoll使用方法

选择材质球 取消网格线 控制手部动作-设置左右手 - 手部运动 控制身材 控制身高 比例

第三节TypeScript 基础类型

1、typescript的基础类型 如下表&#xff1a; 数据类型 关键字 描述 任意类型 any 生命any的变量可以赋值任意类型的值 数字类型 number 整数或分数 字符串类型 string 使用单引号&#xff08;‘’&#xff09;或者双引号&#xff08;“”&#xff09;来表示字符串…

RESTful简介与C/C++实现

一、RESTful简介 RESTful&#xff0c;全称为Representational State Transfer&#xff0c;是一种软件架构风格和设计理念&#xff0c;而不是一种标准。它主要用于Web服务的设计和开发&#xff0c;强调资源的状态表示和状态转移。RESTful风格的设计使得Web服务更加简洁、清晰和…

页面菜单,通过get请求一个url后,跳转另外一个页面,+丢失问题

业务场景描述&#xff1a; 在A系统&#xff0c;菜单点击跳B系统这个操作。 A系统菜单是get请求到B系统的一个缓冲页面&#xff0c;然后这个缓冲页面获取到url中的accessToken后&#xff0c;在这个页面中通过post请求后端接口。 问题描述&#xff1a; 当accessToken中包含了…