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

news2024/7/11 17:02:45

文章目录

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


根据二叉树创建字符串 🦖

题目链接请添加图片描述

🥩 题目描述

给定一个二叉树节点的 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.coloradmin.cn/o/1326219.html

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

相关文章

redis集群介绍

这里写自定义目录标题 redis集群是什么&#xff1f;redis集群方案1.节点2.槽指派3在集群中执行命令4.复制与故障转移5.消息 redis集群搭建参考文档 redis集群是什么&#xff1f; redis集群是一个由多个主从节点群组成的分布式服务集群&#xff0c;它具有复制、高可用和分片特性…

无锡算法大赛线下颁奖会,齐聚院士专家探讨前沿AI技术,大赛选手优秀获奖方案分享!

极市邀您参与“视界拓新知Al有所为 2023首届无锡国际人工智能算法大赛”&#xff0c;相聚美丽无锡&#xff01; 举行地点&#xff1a;无锡君来洲际酒店君来厅 举行时间&#xff1a;2023年12月27日&#xff08;周三&#xff09; 此次大会齐聚多名院士专家&#xff0c;分享前沿…

Linux/Windows IP | Team基础管理

引言 IP&#xff08;Internet Protocol&#xff09; 定义&#xff1a; IP&#xff08;Internet Protocol&#xff09;是网络传输数据的协议&#xff0c;负责在网络中唯一标识和定位设备&#xff0c;并提供数据传输的基础。功能&#xff1a; 允许计算机在网络上相互通信和交换…

做一个家政预约小程序需要了解哪些功能?

随着科技的发展&#xff0c;人们的生活方式发生改变&#xff0c;家政服务在快节奏的时代成为家庭必备。为了满足人们对家政服务的需求&#xff0c;许多家政公司开始寻求线上发展机会。小程序作为轻量级应用&#xff0c;逐渐成为家政行业的重要载体。本文将详细介绍家政小程序的…

【数据结构和算法】---二叉树(1)--树概念及结构

目录 一、树的概念及结构1.1 树的概念1.2 树的相关概念1.3 树的表示1.4 树在实际中的运用 二、二叉树的概念及结构2.1 二叉树概念2.2 特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储结构 三、树概念相关题目 一、树的概念及结构 1.1 树的概念 树是一种非线性的数据结构&#…

Python爬虫之两种urlencode编码发起post请求方式

背景 闲来无事想爬一下牛客网的校招薪资水平及城市分布&#xff0c;最后想做一个薪资水平分布的图表出来 于是发现牛客使用的是application/x-www-form-urlencoded的格式 测试 首先可以先用apipost等测试工具先测试一下是否需要cookie之类的&#xff0c;发现是不需要的&…

第五节TypeScript 运算符

一、描述 运算符用于执行程序代码运算。 二、运算符主要包括&#xff1a; 算术运算符逻辑运算符关系运算符按位运算符赋值运算符三元/条件运算符字符串运算符类型运算符 1、算术运算符 y5&#xff0c;对下面算术运算符进行解释&#xff1a; 运算符 描述 例子 x 运算结果…

Elasticsearch:什么是文本分类?

文本分类定义 - text classification 文本分类是一种机器学习&#xff0c;它将文本文档或句子分类为预定义的类或类别。 它分析文本的内容和含义&#xff0c;然后使用文本标签为其分配最合适的标签。 文本分类的实际应用包括情绪分析&#xff08;确定评论中的正面或负面情绪&…

10 个顶级免费 Android 数据恢复软件可帮助恢复已删除的文件

不小心删除了手机上的一些重要数据或文件&#xff1f;这很不幸&#xff0c;但不要悲伤或放弃希望&#xff0c;因为仍有机会恢复它们。 10 个顶级免费 Android 数据恢复软件 虽然 Android 手机没有像 Windows 那样的回收站可以自动存储您删除的数据&#xff0c;但是有很多功能强…

v高速、低功耗数模转换器MS9708/MS9710/MS9714

产品简述 MS9708/MS9710/MS9714 是一个 8-Bit/10-Bit/14-Bit 高速、低功耗 D/A 转换器。当采样速率达到 125MSPS 时&#xff0c; MS9708/MS9710/MS9714 也能提供优越的 AC 和 DC 性能。 MS9708/MS9710/MS9714 的正常工作电压范围为 2.7V 到 5.5V &#xff0c;…

Python轴承故障诊断 (八)基于EMD-CNN-GRU并行模型的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-CNN-GRU并行模型的轴承故障诊断分类 3.1 训练数据、测试数据分组&#xff0c;数据分ba…

什么是“人机协同”机器学习?

“人机协同”&#xff08;HITL&#xff09;是人工智能的一个分支&#xff0c;它同时利用人类智能和机器智能来创建机器学习模型。在传统的“人机协同”方法中&#xff0c;人们会参与一个良性循环&#xff0c;在其中训练、调整和测试特定算法。通常&#xff0c;它的工作方式如下…

MySql数据库联合查询(MySql数据库学习——六)

本编博客总结了mysql数据库的联合查询&#xff0c;仅用于学习和总结。ฅ ˘ฅ 联合查询&#xff0c;简单的来讲就是多个表联合起来进行查询。 前提条件&#xff1a;这些一起查询的表之间是有关系的&#xff08;一对一、一对多&#xff09;&#xff0c;它们之间一定是有关联字…

【qt信号槽-5】信号槽相关注意事项记录

背景&#xff1a; 信号槽是qt很重要的概念&#xff0c;遇到问题帮助没少看。其中就有signals and slots这一章节&#xff0c;说得很到位。 概念琐碎&#xff0c;记录备忘。不对之处望指正。 【qt信号槽-1】槽函数重写问题&#xff0c;qt_metacall和qt_static_metacall-CSDN博…

【数据结构和算法】定长子串中元音的最大数目

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一&#xff1a;滑动窗口 2.2 方法二&#xff1a;滑动窗口优化版 三、代码 3.1 方法一&#xf…

使用Docker部署Nexus Maven私有仓库并结合Cpolar实现远程访问

文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定Nexus公网地址7. 固定地址访问Nexus Nexus是一个仓库管理工具&#xff0c;用于管理和组织软件构建过程中的依赖项和构件。它与Maven密切相关&#xff0c;可…

CQ 社区版 V2.7.0 发布 | 数据源版本扩充、新增批量执行功能等

2023 年的最后一个社区版本来啦&#xff01;提前祝大家新年快乐~ ✿✿ヽ(▽)ノ✿ 应社区小伙伴的建议&#xff0c;本次版本增加了大量已支持数据源的适配版本&#xff01;&#xff01;&#xff01;&#xff08;是听劝的官方没错&#xff09;同时&#xff0c;新增批量执行、Blo…

4.4 友元

4.4 友元 在程序里&#xff0c;有些私有属性 也想让类外特殊的一些函数或者类进行访问&#xff0c;就需要用到友元的技术 友元的目的是让一个函数或者类 访问另一个类中的私有成员 友元的关键字为 friend 友元的三种使用场景 全局函数做友元类做友元成员函数做友元 4.4.1…

【数组Array】力扣-1094 拼车

目录 题目描述 解题过程 题目描述 车上最初有 capacity 个空座位。车 只能 向一个方向行驶&#xff08;也就是说&#xff0c;不允许掉头或改变方向&#xff09; 给定整数 capacity 和一个数组 trips , trip[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassen…

详解数据科学自动化与机器学习自动化

过去十年里&#xff0c;人工智能&#xff08;AI&#xff09;构建自动化发展迅速并取得了多项成就。在关于AI未来的讨论中&#xff0c;您可能会经常听到人们交替使用数据科学自动化与机器学习自动化这两个术语。事实上&#xff0c;这些术语有着不同的定义&#xff1a;如今的自动…