<C++> 二叉树进阶OJ题

news2024/11/10 13:00:06

目录

1. 二叉树创建字符串

2. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 

3. 二叉树搜索树转换成排序双向链表

4. 根据一棵树的前序遍历与中序遍历构造二叉树

5. 根据一棵树的中序遍历与后序遍历构造二叉树

6. 二叉树的前序遍历,非递归迭代实现 

7. 二叉树中序遍历 ,非递归迭代实现

8. 二叉树的后序遍历 ,非递归迭代实现

 1. 二叉树创建字符串

  • 此题先遍历左子树,再遍历右子树即可
  • 问题在于如何判断在什么时候加括号:如果该节点左边为空,右不为空,则左边括号保留。如果左边为空,右边也为空,那么左右括号都不保留。如果左边不为空,右边为空,则右边括号不保留 

  • 创建string型变量str,将每一个节点的int 数据to_string为string类型,再由递归层层向下,最终在return之后str全都加起来
class Solution {
public:
    string tree2str(TreeNode* root) {
        if (root == nullptr)
            return "";

        string str = to_string(root->val);

        //如果该节点左边为空,右不为空,则左边括号保留
        //如果左边不为空,右边为空,则右边括号不保留 
        if (root->left || root->right)
        {
            str += '(';
            str += tree2str(root->left);
            str += ')';
        }

        if (root->right)
        {
            str += '(';
            str += tree2str(root->right);
            str += ')';
        }

        return str;
    }
};

2. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 

  • 如果为三叉链结构(含有父亲节点指针),那么求解公共祖先非常简单,类似求双交链表,将parent指针视为next指针,让底层的节点先走差距步,然后再一个一个的比较,当两个节点相遇时该节点就是公共祖先
  • 如果是普通的二叉链式结构, 通过find查找p、q是在左还是在右。因为只要存在一个节点,p、q分别在它的左子树和右子树,那么该节点就是p、q的最近公共祖先!

  • 如果root节点是p或q,那么root就是公共祖先(自己可以是自己的祖宗)

class Solution {
public:
    bool find(TreeNode* r, TreeNode* x)
    {
        if (r == nullptr)
            return false;
        
        return r == x || find(r->left, x) || find(r->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 && qInLeft)
        {
            return lowestCommonAncestor(root->left, p, q);
        }
        else if (pInRight && qInRight)
        {
            return lowestCommonAncestor(root->right, p, q);
        }
        else
        {
            return root;
        }
    }
};

该算法时间复杂度是什么?

        O(N*N),猜测该树状结构为最坏情况,全都在一条分支,每一次都find,最坏在高度次找到

如何优化到 O(N) ?

        使用前序遍历,用stack来记录到达p、q路径上的节点(比较相等时,比较指针,因为树内可能有相等的值),最后转化为类似相交链表的问题

         左右都找不到,那么就pop掉该节点的,因为该节点不在p、q的路径上

    //方法二:前序遍历获取p、q路径,用stack记录路径上节点,最后转化为相交链表问题
    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;

        //左右都找不到,就会到这里,那么就pop掉该节点的,因为该节点不在p、q的路径上
        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())
        {
            pPath.pop();
        }

        while (pPath.size() < qPath.size())
        {
            qPath.pop();
        }

        while (pPath.top() != qPath.top())
        {
            pPath.pop();
            qPath.pop();
        }

        return qPath.top();

    }

3. 二叉树搜索树转换成排序双向链表

在诸多要求下,该如何链接?

        创建两个指针,prev、cur,然后采用中序遍历(中序遍历的顺序就是双链表的顺序),cur先走,prev在后,到达一个节点时,它的上一个节点 prev 的 right 链接 cur,cur 的 left 链接 prev,然后更新prev,继续递归

        在中序遍历时,要注意参数prev的类型,是TreeNode*的引用!这是为了在树最底层的栈帧中prev被更新返回上一个函数栈帧后,prev也是新的,可以被使用

        链接完成后,找到左子树最左节点,该节点就是head,返回该节点就是答案

class Solution {
public:
	void InOrder(TreeNode* cur, TreeNode*& prev)
	{
		if (cur == nullptr)
			return;

		//中序遍历
		InOrder(cur->left, prev);

		//开始链接
		cur->left = prev;
		if (prev)
			prev->right = cur;
		//更新prev,注意参数要用&,因为所有递归函数要用统一的prev,prev更新之后可以立即使用
		prev = cur;

		InOrder(cur->right, prev);
	}

    TreeNode* Convert(TreeNode* pRootOfTree) 
	{
		TreeNode* prev = nullptr;
		InOrder(pRootOfTree, prev);

		TreeNode* head = pRootOfTree;
		while (head && head->left)
			head = head->left;

		return head;
    }
};

4. 根据一棵树的前序遍历与中序遍历构造二叉树

  • 前序序列(根 左子树 右子树):用来确定根

  • 中序序列(左子树 根 右子树):根据根来分割左右区间,当区间不存在时分割完成
  • 让控制先序遍历数组的下标 prei 一直往后走,再在中序遍历的数组中找到对应的值,根据这个值的下标分割数组,将子区间各自递归下去
class Solution {
public:
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei,
        int inbegin, int inend)
        {
            //如果区间不存在,则分割完成
            if (inbegin > inend)
                return nullptr;
            
            TreeNode* root = new TreeNode(preorder[prei]);
            
            //在中序数组内找,prei对应的根
            int rooti = inbegin;
            while (rooti <= inend)
            {
                if (inorder[rooti] == preorder[prei])
                    break;
                ++rooti;
            }

            //往后走,分割区间继续递归
            ++prei;
            //链接是从低到顶链接的,即递归到最底往上的时候开始链接
            //[inbegin, rooti - 1] rooti [rooti + 1, inend]
            root->left = _buildTree(preorder, inorder, prei, inbegin, rooti - 1);
            root->right = _buildTree(preorder, inorder, prei, rooti + 1, inend);;

            return root;

        } 


    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        int i = 0;
        TreeNode* root = _buildTree(preorder, inorder, i, 0, inorder.size() - 1);

        return root;
    }
};

5. 根据一棵树的中序遍历与后序遍历构造二叉树

  • 同理,有中序和后序,由于后序(左子树 右子树 根)的特性,控制下标从后往前走,并先构建右子树
class Solution {
public:
    TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder, int& posti, int inbegin, int inend) 
    {
        if (inbegin > inend)
            return nullptr;

        TreeNode* root = new TreeNode(postorder[posti]);
        int rooti = inbegin;
        while (rooti <= inend)
        {
            if (inorder[rooti] == postorder[posti])
                break;
            ++rooti;
        }
        --posti;

        root->right = _buildTree(inorder, postorder, posti, rooti + 1, inend);
        root->left = _buildTree(inorder, postorder, posti, inbegin, rooti - 1);

        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) 
    {
        int i = postorder.size() - 1;
        TreeNode* root = _buildTree(inorder, postorder, i, 0, inorder.size() - 1);

        return root;
    }
};

6. 二叉树的前序遍历,非递归迭代实现 

  • 访问一棵树,分为两部分:左路节点,左路节点的右子树,这一点很重要,有递归思想,是解题关键
  • 用栈来实现前序遍历,将左路节点的指针入栈,将访问的值存入vector
  • 开始访问右子树时先将栈顶元素出栈,以子问题的方式访问右子树,循环起来

依托于栈的特性,后进先出,使得访问顺序就是前序 

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) 
    {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        vector<int> v;
        while (cur || !st.empty())
        {
            //访问一颗树的开始
            //访问左路节点,将所有root的所有左路节点入栈,后序在访问左路节点的右子树
            while (cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur = cur->left;
            }

            //依次访问左路节点的右子树
            TreeNode* top = st.top();
            st.pop();

            //以子问题的方式访问右子树
            cur = top->right;
        }
        return v;
    }
};

7. 二叉树中序遍历 ,非递归迭代实现

  • 很简单,将v的push_back放在出栈时就可以打到中序遍历的效果
  • 从栈里面pop一个节点,意味着这个节点的左子树已经访问完了
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) 
    {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        vector<int> v;
        while (cur || !st.empty())
        {
            //访问一颗树的开始
            //访问左路节点,将所有root的所有左路节点入栈,后序在访问左路节点的右子树
            while (cur)
            {

                st.push(cur);
                cur = cur->left;
            }

            //依次访问左路节点的右子树
            TreeNode* top = st.top();
            st.pop();
            
            v.push_back(top->val);
            //以子问题的方式访问右子树
            cur = top->right;
        }
        return v;
    
    }
};

8. 二叉树的后序遍历 ,非递归迭代实现

  • 按照后序遍历,如果一个节点的右子树没有访问过(正准备访问),那么它的上一个访问节点是左子树的根;如果让的右子树已经访问过了(刚刚访问完),那么它的上一个访问节点是右子树的根
  • 所以后序遍历的访问时机就是,如果该节点访问到第二次了,那么这个节点的val就要被push_back进vector中了,或者如果该节点没有右子树,那么这个节点的val也要被push_back进vector中
  • 所以如何判断该节点已经访问过两次了?答案就是用prev记录上一个已经访问的节点!
  • 一个节点的右子树为空或者上一个访问的节点是右子树的根,都说明该节点的右子树已经访问完了,可以访问当前节点了!

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> v;

        TreeNode* prev = nullptr;
        TreeNode* cur = root;

        while (cur || !st.empty())
        {
            //访问一颗树的开始
            //访问左路节点,将所有root的所有左路节点入栈,后序在访问左路节点的右子树
            while (cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            TreeNode* top = st.top();
            //一个节点的右子树为空或者上一个访问的节点是右子树的根
            //都说明该节点的右子树已经访问完了,可以访问当前节点了!
            if (top->right == nullptr || top->right == prev)
            {
                prev = top;
                v.push_back(top->val);
                st.pop();
            }
            else cur = top->right;
        }
        return v;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073367.html

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

相关文章

13.全志H3-底板测试和测试记录

上面是我的微信和QQ群&#xff0c;欢迎新朋友的加入。 前段时间&#xff0c;做了一个H3的双DDR方案&#xff0c;记录一下测试过程。 1.硬件速率检测 1.取掉SD卡&#xff0c;不接调试串口&#xff0c;按住复位按键&#xff0c;上电 设备管理器会弹出USB设备&#xff0c;打开硬…

Python|日志记录详解(1)

引言 作为一名Python程序员&#xff0c;记录程序运行时的关键信息是一种有益的做法&#xff0c;这有助于深入理解你的代码。这种记录行为被称作日志记录&#xff0c;它是一个非常实用的工具&#xff0c;是编程工具箱中不可或缺的一部分。日志记录能够帮助你捕捉到在开发过程中可…

哈里和梅根的批评者似乎并不理解“不可征服运动会”的全球影响力

哈里王子和梅根马克尔的批评者似乎无法理解为什么这对夫妇继续像王室之旅一样环游世界。答案很简单&#xff1a;不可征服运动会。苏塞克斯夫妇通过这项体育赛事获得了成功&#xff0c;它为“国际受伤、受伤和生病的军人和女性提供了康复之路”。各国正在争夺未来运动会的主办权…

Photoshop用户必备:PS全套插件分享(PS插件全家桶)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 PS全套插件 📒🎈 获取方式 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 你是否时常为Photoshop中复杂的操作流程感到困扰,渴望拥有一把钥匙,能瞬间解锁设计效率与创意的新世界?今天,我将给大家分享一款让众多设计师梦寐以求的…

Elasticsearch用法

&#x1f4a5; 该系列属于【SpringBoot基础】专栏&#xff0c;如您需查看其他SpringBoot相关文章&#xff0c;请您点击左边的连接 目录 一、初识elasticsearch 1. 安装Elasticsearch和Kibana 2. 倒排索引 3. IK分词器 4. 基础概念 二、索引库操作 1. Mapping映射属性 2…

IM项目:进阶版即时通讯项目---语音识别和验证码服务

文章目录 语音识别基本功能模块划分流程图实现逻辑解析proto文件 验证码服务 本篇主要是对于该项目的语音识别和验证码服务模块的一个梳理&#xff0c;项目会直接使用部分封装好的内容&#xff0c;可以查看前面的文档或在本文档中进行查看和学习 由于这两个模块非常相似&#…

模型 卡茨管理模型

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。技术、人际、概念&#xff1a;管理技能三支柱。 1 卡茨管理模型的应用 1.1 产品经理能力模型&#xff1a;基于卡茨管理模型的应用 在现代企业管理中&#xff0c;产品经理的角色越来越受到重视。根据…

HTML简单了解和基础知识记录

参考视频 html的用途 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;&#xff0c;用来显示网页的文字和框架结构&#xff0c;可以认为是网页的骨架。 标签/元素 用于定义文字图片连接等&#xff0c;分…

超简单Java邮件发送-为你的小程序实现邮箱验证码登录

本项目基于Apache Commons Email for Jakarta开发。 超简单实现邮箱发送&#xff0c;并以此扩展出邮箱验证码功能(请读者扩展实现)。 构建工具&#xff1a;Maven 演示邮箱&#xff1a;outlook 准备 请确保你的邮箱开启了SMTP服务&#xff0c;并获取相关参数&#xff0c;类似…

51单片机——模块化编程

1、模块化编程介绍 传统方式编程&#xff1a;所有的函数均放在main.c里&#xff0c;若使用的模块比较多&#xff0c;则一个文件内会有很多的代码&#xff0c;不利于代码的组织和管理&#xff0c;而且很影响编程者的思路。 模块化编程&#xff1a;把各个模块的代码放在不同的.…

诊断知识:agedDTCLimit的使用

文章目录 前言agedDTCLimit的定义agedDTCLimit的使用图解agedDTCLimit总结 前言 ECU中的存储空间一般无法将所有的DTC同时记录故障数据&#xff0c;所以故障发生之后记录的数据&#xff0c;需要在一段时间后未出现故障则自动清除&#xff0c;以保证新的故障出现时&#xff0c;…

苍穹外卖-day05(SpringBoot+SSM的企业级Java项目实战)

苍穹外卖-day05 课程内容 Redis入门 Redis数据类型 Redis常用命令 在Java中操作Redis 店铺营业状态设置 功能实现&#xff1a;营业状态设置 效果图&#xff1a; 选择营业中&#xff0c;客户可在小程序端下单&#xff1a; 选择打烊中&#xff0c;客户无法在小程序端下单&…

【Docker】Docker学习04 | dockerfile的编写

本文首发于 ❄️慕雪的寒舍 dockerfile是构建docker镜像的基础&#xff0c;它规定了一系列语法&#xff0c;让我们可以在某个基础镜像之上&#xff0c;添加自己需要的操作&#xff0c;打包出一个自己的镜像。 1. dockerfile基本语法 下面是dockerfile的基本语法和其对应的功能…

【算法进阶2-动态规划】斐波那契数列(递归调用、动态规划)、钢条切割问题(自定而下实现、自底向上、切割方案)

1 斐波那契数 2 钢条切割问题 2.1 最优解情况 2.2 钢条切割问题之自定而下实现 2.3 钢条切割问题之自底向上实现 2.4 钢条切割问题-重构解-切割方案 1 斐波那契数 # 1 子问题的重复计算 def fibonacci(n: int) -> int:"""使用递归方式计算第 n 个斐波那契数…

计算机毕业设计选题推荐-法律援助平台-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

有限差分学习笔记

有限差分介绍 ​ 在数学中&#xff0c;有限差分法&#xff08;finite-difference methods&#xff0c;简称FDM&#xff09;&#xff0c;是一种微分方程数值方法&#xff0c;是通过有限差分来近似导数&#xff0c;从而寻求微分方程的近似解。 由泰勒展开式的推导 显式方…

给Go+Sciter开发的桌面客户端软件添加系统托盘图标

在桌面端软件开发中&#xff0c;系统托盘图标是提升用户体验的重要元素。托盘图标不仅能提供直观的状态反馈&#xff0c;还能让软件在后台运行时依然保持与用户的交互。通过托盘图标&#xff0c;用户可以轻松最小化软件、退出程序&#xff0c;甚至弹出通知&#xff0c;从而避免…

【海外EI 会议合集】电网系统/绿色能源/新材料主题均可

第五届电网系统与绿色能源国际学术会议&#xff08;PGSGE 2025&#xff09; 2025 5th International Conference on Power Grid Systems and Green Energy 重要信息 会议官网&#xff1a;www.pgsge.org 会议时间&#xff1a;2025年1月10-12日 会议地点&#xff1a;马来西亚…

Linux 部署 MinIO(远程服务器)

1. 下载安装 进入 Linux 内 cd /usr/local/ # 新建目录 mkdir minio # 进入目录 cd minio # 下载地址 wget https://dl.min.io/server/minio/release/linux-amd64/minio# 授权 chmod x minio 2. 自定义配置 自定义账号与登录密码&#xff0c;直接在本目录 默认登录账号和…