二叉树进阶OJ题

简介: 二叉树进阶OJ题



一、前序遍历非递归

题目描述:写出二叉树前序遍历的非递归形式。

链接:前序遍历

思路:任何一颗树都可以认为分为左路节点,左路节点的右子树。先访问左路节点,再来访问左路节点的右子树。

先定义栈s存放节点,vector v来存放结点的数值,TreeNode* cur,cur初始化为root。

当cur不为空或者栈不为空的时候(一开始栈是空的,cur不为空),循环继续:先把左路节点存放进栈中,同时把值存入v中,一直循环,直到此时的左路节点为空,访问结束。在弹出栈顶元素top,把top->right赋值给我们的cur,就可以转化成子问题去访问左路节点的右子树了。

~  栈s不为空说明此时还有左路节点的右子树还没访问,cur不为空说明此时还有树要去访问。当两个同时为空时,循环结束,最终得到前序遍历。

~  一个节点出栈说明这个节点及其左子树已经访问完了,因为我们是先把左路节点存入栈中,此时还剩右子树没有访问。

代码实现:

class Solution 
{
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        while(cur || !s.empty())
        {
            while(cur)
            {
                s.push(cur);
                v.push_back(cur->val);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            s.pop();
            cur = top->right;
        }
        return v;
    }
};

二、中序遍历非递归

题目描述:写出二叉树中序遍历的非递归形式。

链接:中序遍历

思路:中序遍历是左、根、右。左子树访问完之后才能去访问根。左路节点一直走直到左子树访问完,入栈的过程中不去进行访问(存放数值到v中),当左路节点出栈之后,也就是从栈中弹出进行访问。

代码实现:

class Solution 
{
public:
    vector<int> inorderTraversal(TreeNode* root) 
    {
        vector<int> v;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur || !s.empty())
        {
            while(cur)
            {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            v.push_back(top->val);
            s.pop();
            cur = top->right;
        }
        return v;
    }
};

三、后序遍历非递归

题目描述:写出二叉树后序遍历的非递归形式。

链接:后序遍历

思路:我们定义一个栈,在栈里面取到一个节点时:右子树是否访问过,如果没有访问,迭代子问题访问,如果访问过了,则访问这个根节点,pop出栈。

如果top的右子树为空或者右子树已经访问过了(上一个访问节点是右子树的根),那么说明右子树不用访问或者访问过了,可以访问根top;当右子树不为空,且没有访问过,则迭代子问题访问。

通过prev来判断上一次访问的节点:如果prev等于top->right时,表示栈顶节点的右子树已经访问过了,可以弹出栈顶节点并访问它。

代码实现:

class Solution 
{
public:
    vector<int> postorderTraversal(TreeNode* root) 
    {
        stack<TreeNode*> s;
        vector<int> v;
        TreeNode* cur = root;
        TreeNode* prev = nullptr;
        while(cur || !s.empty())
        {
            while(cur)
            {
                s.push(cur);
                cur = cur->left;
            }
            TreeNode* top = s.top();
            if(top->right == nullptr || top->right == prev)
            {
                v.push_back(top->val);
                prev = top;
                s.pop();
            }
            else
            {
                cur = top->right;
            }
        }
        return v;
    }
};

四、二叉树转链表

题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。

注意:

1、要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继

2、返回链表中的第一个节点的指针

3、函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

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

链接:二叉树转链表

代码实现:

class Solution 
{
public:
  void InorderConvert(TreeNode* cur, TreeNode*& prev)
  {
    if(cur == nullptr)
      return;
    InorderConvert(cur->left, prev);
    cur->left = prev;
        if(prev)
      prev->right = cur;
    prev = cur;
    InorderConvert(cur->right, prev);
  }
    TreeNode* Convert(TreeNode* pRootOfTree) 
    {
        TreeNode* prev = nullptr;
    InorderConvert(pRootOfTree, prev);
    TreeNode* head = pRootOfTree;
        while(head && head->left)
    {
      head = head->left;
    }
    return head;
    }
};

五、二叉树的最近公共祖先

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

链接:二叉树的公共祖先

方法一:

思路:把根到对应节点的路径存储起来,在找出相交的结点即是最近的公共结点。

代码实现:

class Solution 
{
public:
    bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
    {
        if(root == nullptr)
            return false;
        path.push(root);
        if(root == x)
            return true;
        if(FindPath(root->left, x, path))
            return true;
        if(FindPath(root->right, x, path))
            return true;
        path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        stack<TreeNode*> pPath, qPath;
        FindPath(root, p, pPath);
        FindPath(root, q, qPath);
        while(pPath.size() != qPath.size())
        {
            if(pPath.size() > qPath.size())
                pPath.pop();
            else
                qPath.pop();
        }
        while(pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }
        return qPath.top();
    }
};

方法二:

思路:我们先分析下题干,可以知道:要找两个节点的公共祖先,我们通过规律发现这两个节点一定是分别在它们的公共祖先的左子树和右子树。

那么有了这个规律,我们就可以这样去找:当遍历到一个节点时,如果p在它的右子树且q在它的左子树或者p在左子树且q在右子树,那么它就是公共祖先。如p,q在同一侧,那么该节点一定不是p,q的公共祖先,继续递归寻找,如果p,q在左子树,就到左子树中找,想反的就去右子树中找。

代码实现:

class Solution 
{
public:
    bool Find(TreeNode* root, TreeNode* x)
    {
        if(root == nullptr)
            return false;
        if(root == x)
            return true;
        return Find(root->left, x) || Find(root->right, x);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) 
    {
        if(root == nullptr)
            return nullptr;
        if(root == p || root == q)
            return root;
        bool pInleft, pInright, qInleft, qInright;
        pInleft = Find(root->left, p);
        pInright = !pInleft;
        qInleft = Find(root->left, q);
        qInright = !qInleft;
        if((pInleft && qInright) || (pInright && qInleft))
            return root;
        else if(pInleft && qInleft)
            return lowestCommonAncestor(root->left, p, q);
        else if(pInright && qInright)
            return lowestCommonAncestor(root->right, p, q);
        else
            return nullptr;
    }
};

六、二叉树的层序遍历1

题目描述:给你二叉树的根节点 root ,返回其节点值的 层序遍历。

链接:二叉树的层序遍历

思路:既然是层序遍历,根据我们之前的经验,我们可以使用队列+循环的方式进行控制。但是,我们需要把每一层的数放在同一个数组中(不同层的数要在不同的数组中)。那么我们应该怎么来表示当前层的数已经全部进入同一个数组了呢?

以下面的图为例。首先,我们要明确的是:第一层只有一个节点,所以第一个数组只有一个数,所以只循环一次;在第一层的节点出队列后,第二层的节点已经进入队列,此时队列的元素个数就是第二层的元素个数,而第二层有两个节点,需要循环两次。

这里我们可以定义一个变量 LevelSize,来帮助我们记录每一层的节点个数。根据上面的规律,我们知道:每一层要循环节点个数次,当LevelSize为0时,表示该层已经遍历完,然后更新LevelSize的大小为队列元素个数。

代码实现:

class Solution 
{
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        queue<TreeNode*> q;
        vector<vector<int>> vv;
        int LeverSize = 1;
        if(root != nullptr)
        {
            q.push(root);
        }
        while(!q.empty())
        {
            vector<int> v;
            while(LeverSize--)
            {
                TreeNode* cur = q.front();
                if(cur->left != nullptr)
                    q.push(cur->left);
                if(cur->right != nullptr)
                    q.push(cur->right);
                q.pop();
                v.push_back(cur->val);
            }
            vv.push_back(v);
            LeverSize = q.size();
        }
        return vv;
    }
};

七、二叉树的层序遍历2

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

链接:二叉树的层序遍历2

思路:最简单的方法就是使用题目六中的遍历方法,最后用逆置算法将数组逆置即可。

代码实现:

class Solution 
{
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) 
    {
        queue<TreeNode*> q;
        vector<vector<int>> vv;
        int LeverSize = 1;
        if(root != nullptr)
        {
            q.push(root);
        }
        while(!q.empty())
        {
            vector<int> v;
            while(LeverSize--)
            {
                TreeNode* cur = q.front();
                if(cur->left != nullptr)
                    q.push(cur->left);
                if(cur->right != nullptr)
                    q.push(cur->right);
                q.pop();
                v.push_back(cur->val);
            }
            vv.push_back(v);
            LeverSize = q.size();
        }
        reverse(vv.begin(), vv.end());
        return vv;
    }
};
目录
相关文章
|
测试技术 开发工具 Android开发
跨平台的视频采集、直播SDK SmarterStreaming
SmarterStreaming 跨平台的视频采集、直播SDK(支持Windows/android/iOS,支持私有协议和RTMP推流),也许是国内最靠谱的视频直播推流、播放SDK之一,助您轻松实现类似于花椒、映客、斗鱼手机直播推送与播放。
2147 0
|
10月前
|
存储 供应链 数据可视化
惊艳!2025 蛇年新春汉服租赁管理软件哪家强?实测告诉你!
随着汉服热潮升温,2025蛇年新春临近,汉服制作与租赁行业迎来业务高峰。MBTI-J型管理者需高效协作工具,可视化团队协作软件成关键。本文推荐6款精品软件:板栗看板、Miro、Asana、Notion、Slack和Airtable。这些工具分别在流程管理、创意协作、任务分配、知识沉淀、沟通优化及数据统筹等方面各显神通,助力汉服企业提升效率、精准决策,确保新春活动顺利开展,推动品牌发展。
208 5
|
7月前
|
人工智能 供应链 监控
数字化转型具体应该从哪里入手?——生成式AI时代的战略行动框架
本文探讨生成式AI技术对数字化转型的深远影响,从战略认知、技术赋能、组织能力三个维度构建转型框架。强调需突破传统技术工具论,将AI视为战略伙伴;从数据驱动转向知识进化;由组织变革拓展至生态重构。同时提出具体实施路径,包括场景优先级排序、人机协作设计及技术债务管理,并结合培生认证项目阐述其在能力基准建立、战略合作与创新生态接入的战略价值,助力企业实现持续进化与价值创造的本质回归。
|
12月前
|
机器学习/深度学习 人工智能 Anolis
手把手教学攻略:在Anolis OS上部署OpenVINO深度学习模型
Anolis OS 作为国内首个正式提供 OpenVINO 开发包和镜像的服务器端操作系统,推动国内 AI 推理生态和能力的升级。
|
11月前
|
搜索推荐 算法 数据挖掘
淘口令真实URL API接口的应用与收益
淘口令作为电商推广利器,通过简短文本引导用户直达商品页,提升购物体验与销售效率。本文探讨淘口令真实URL API接口的应用,包括商品推广、数据分析、跨境电商及社交媒体营销等方面,揭示其在电商领域的巨大潜力与收益。
244 1
|
存储 安全 物联网
裸金属服务器适合哪些场景使用,有哪些优势
裸金属架构虚拟系统无需安装操作系统或虚拟化软件,而是通过虚拟化技术直接将硬件资源分配给应用,这种架构消除了传统虚拟化技术中的操作系统层,使虚拟机能够直接访问物理硬件资源,实现了更高的性能和更低的延迟,从而提供接近物理机的性能和效率。
|
敏捷开发 算法 测试技术
【软件测试】 测试用例的基本要素与设计方法
【软件测试】 测试用例的基本要素与设计方法
|
开发框架 搜索推荐 .NET
ASP.NET体检中心源码,实现检前、检中、检后全流程管理
健康体检系统遵循整个健康体检的实际流程,以提高工作效率、降低错检、防止漏检提高人性化服务水平为目的,在体检过程中可以高效、自动化、人性化的处理数据与提供服务。针对体检流程中工作强度在时间分配上不均匀等特点,解决了体检信息处理效率问题,在不增加体检中心人力资源投入或少投入的基础上,提升信息处理的效率,从而突破体检中心日处理体检人数的上限,为体检中心创造更大经济效益的同时,还能有效的降低体检工作者的劳动强度。
301 5
|
存储 Ubuntu Linux
嵌入式Linux系列第5篇:Nand Flash根文件系统制作
嵌入式Linux系列第5篇:Nand Flash根文件系统制作