二叉树oj题

news2025/1/22 12:39:35

目录

层序遍历(一)

题目

思路

代码

层序遍历(二)

题目

思路

代码

根据二叉树创建字符串

题目

思路

代码

二叉树的最近公共祖先

题目

思路

代码

暴力版

队列版

栈版

bs树和双向链表

题目

思路

代码

前序中序序列构建二叉树

题目

思路

代码 

中序后序序列构建二叉树

题目

思路

代码 

非递归前序遍历

题目

思路

代码

非递归中序遍历

题目

思路

代码

非递归后序遍历

题目

思路

代码


层序遍历(一)

题目

102. 二叉树的层序遍历 - 力扣(LeetCode)

思路

之前写过层序遍历,思路就是用队列存储结点,每次出一层,然后入出的结点的子结点

重点就是要记录一层的结点个数,然后出相应个数的结点

代码

    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> arr;
        queue<TreeNode*> q;
        if(root!=nullptr){
            q.push(root);
        }
        while(!q.empty()){
            vector<int> tmp; //存储一层的结点
            int size=q.size(); //此时队列内的元素就是上一层的结点个数
            for(int i=0;i<size;++i){
                tmp.push_back(q.front()->val);
                if(q.front()->left){ //有子树就放进队列中
                    q.push(q.front()->left);
                }
                if(q.front()->right){
                    q.push(q.front()->right);
                }
                q.pop(); //出掉这个父结点
            }
            arr.push_back(tmp);
        }
        return arr;
    }

层序遍历(二)

题目

107. 二叉树的层序遍历 II - 力扣(LeetCode)

思路

看似好像很麻烦,其实只要把上一道题得到的答案逆置就行了

代码

    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> arr;
        queue<TreeNode*> q;
        if(root!=nullptr){
            q.push(root);
        }
        while(!q.empty()){
            vector<int> tmp;
            int size=q.size();
            for(int i=0;i<size;++i){
                tmp.push_back(q.front()->val);
                if(q.front()->left){
                    q.push(q.front()->left);
                }
                if(q.front()->right){
                    q.push(q.front()->right);
                }
                q.pop();
            }
            arr.push_back(tmp);
        }
        reverse(arr.begin(),arr.end());  //逆置
        return arr;
    }

根据二叉树创建字符串

题目

606. 根据二叉树创建字符串 - 力扣(LeetCode)

思路

基础代码还是一个前序遍历,只不过需要在打印时需要好好处理一下

  • 首先,不能有不必要的括号,比如有左树无右树 / 左右子树全无
  • 但是要注意,当无左树有右树时,需要将左树的空括号打印出来

代码

    void front_order(TreeNode* root,string& ans){
        if(root==nullptr){
            return ;
        }
        ans+=to_string(root->val);
        if(root->left||root->right){ 
    //左需要特殊判断,左右均有/只有右/只有左,都需要打印左,即使是空括号
            ans+='(';
            front_order(root->left,ans);
            ans+=')';
        }
        if(root->right){ //右只需要在它存在时打印
            ans+="(";
            front_order(root->right,ans);
            ans+=')';
        }
    }
    string tree2str(TreeNode* root) {
        string ans;
        front_order(root,ans);
        return ans;
    }

二叉树的最近公共祖先

题目

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

思路

  • 如果要找的两个结点分别在某一结点的左右,说明该结点就是公共结点
  • 如果两个结点都在某结点的左子树,那么该结点不会是公共结点,且确定公共结点需要在该结点的左子树中寻找 ; 右子树同理
  • 如果某结点就是要找的两个结点的其中一个结点

除了直接找结点位置,也可以将结点路径存储起来,然后按照找相交链表的交点那样(先从尾巴出[相差个数]个结点),找出公共结点

队列/栈储存都是一样的

代码

暴力版

    bool find(TreeNode* root, TreeNode* p){
        if(root==nullptr){
            return false;
        }
        if(root==p){
            return true;
        }
        return find(root->left,p)||find(root->right,p); //只要一边找到了就返回真
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr){
            return nullptr;
        }
        if(root==p||root==q){
            return root;
        }
        //判断当前结点下,要找结点的相对位置
        bool pleft=find(root->left,p);
        bool pright=!pleft;
        bool qleft=find(root->left,q);
        bool qright=!qleft;
        //如果都在左,去左找
        if(pleft&&qleft){
            return lowestCommonAncestor(root->left,p,q);
        }
        //如果都在右,去右找
        else if(pright&&qright){
            return lowestCommonAncestor(root->right,p,q);
        }
        //走到这里,说明一左一右
        else{
            return root;
        }
    }

队列版

 bool find_q(TreeNode* root, TreeNode* p,queue<TreeNode*>& ans){
        if(root==nullptr){
            return false;
        }
        if(root==p){ 
            ans.push(root); //队列:先入子结点,保证先出的是子结点
            return true;
        }
        if(find_q(root->left,p,ans)){ //为真就存储起来(说明当前结点是路径上的一点)
            ans.push(root);
            return true;
        }
        if(find_q(root->right,p,ans)){ //同理
            ans.push(root);
            return true;
        } 
        return false; //走到这里,说明左右均没找到,就说明这条路没有要找的结点
    } 
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        queue<TreeNode*> sp,sq;
        find_q(root,p,sp);
        find_q(root,q,sq);
        //先将多的出掉
        while(sp.size()>sq.size()){
            sp.pop();
        }
        while(sp.size()<sq.size()){
            sq.pop();
        }
        //然后同时出,直到找到那个公共结点
        while(sp.front()!=sq.front()){
            sp.pop();
            sq.pop();
        }
        return sp.front();
    }

栈版

    bool find_s(TreeNode* root, TreeNode* p,stack<TreeNode*>& ans){
        if(root==nullptr){
            return false;
        }
        ans.push(root); //注意:栈必须得先入父结点,保证先出的是子结点
        if(root==p){
            return true;
        }
        if(find_s(root->left,p,ans)){
            return true;
        }
        if(find_s(root->right,p,ans)){
            return true;
        }
        ans.pop();
        return false;
    }
    
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> sp,sq;
        find_s(root,p,sp);
        find_s(root,q,sq);
        while(sp.size()>sq.size()){
            sp.pop();
        }
        while(sp.size()<sq.size()){
            sq.pop();
        }
        while(sp.top()!=sq.top()){
            sp.pop();
            sq.pop();
        }
    }

bs树和双向链表

题目

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

思路

仔细看转化后的双向链表,结点顺序其实就是中序遍历的结点顺序

所以这道题的代码就是在中序遍历的基础上进行处理

  • 因为需要指向前驱和后继,所以至少得搞一个变量,存上一个结点
  • (毕竟没法知道下一个结点是啥,但上一个还是可以的,就像之前拿到父结点那样)
  • 那么前驱搞定了,后继呢?
  • 其实知道后继也可以,只要穿越到未来,再回到过去就可以知道了
  • 所以,我们实际上知道的后继是上一个结点的,也就是当前结点 (当前结点就是上一个结点的后继)
  • 还要注意一点,我们找到的第一个结点是最左端的,所以它没有前驱
  • 所以首次传入的prev是空指针(prev赋值给结点的前驱)

代码

	void inorder(TreeNode* root,TreeNode*& prev){
		if(root==nullptr){
			return;
		}
		inorder(root->left,prev);
		root->left=prev;
		if(prev){ //prev会为空,所以需要判断,不然就越界了
			prev->right=root;
		}
		prev=root; //因为之后要进root的后继结点了,所以需要更新prev
		inorder(root->right,prev);
	}
    TreeNode* Convert(TreeNode* pRootOfTree) {
		if(pRootOfTree==nullptr){
			return nullptr;
		}
		TreeNode *prev=nullptr,*tmp=pRootOfTree;
		while(tmp->left!=nullptr){ //拿到链表的第一个结点
			tmp=tmp->left;
		}
		inorder(pRootOfTree,prev);
		return tmp;
    }

前序中序序列构建二叉树

题目

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

思路

和手动构建树的顺序一样,前序确定根,中序确定根的左右子树,只不过需要用代码写出来

  • 因为每次都需要将中序数组分成两个(是不是很像快排里面的操作),所以我们还是使用递归来写
  • 但要注意,我们还需要一个不断增加的下标来访问前序(中序需要使用两个下标作为范围,它直接传值就行)

代码 

class Solution {
public:
    TreeNode* _build(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend) {
        if (inbegin > inend) { //数组不合法,说明不存在这个结点,所以返回空
            return nullptr;
        }
        TreeNode* node = new TreeNode(preorder[prei]); //按照前序构建二叉树(因为先拿到的都是根结点)
        int begin = inbegin;
        // while(preorder[prei]!=inorder[begin]&&begin<=inend){
        //     begin++;
        // }
        for (; begin <= inend; ++begin) { //在中序中找到根结点
            if (preorder[prei] == inorder[begin]) {
                break;
            }
        }
        ++prei; //拿到下一个结点
        node->left = _build(preorder, inorder, prei, inbegin, begin - 1);//在找到的根结点,左区间是左子树
        node->right = _build(preorder, inorder, prei, begin + 1, inend);//同理
        return node;//左右子树构建完毕后,返回该结点
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int prei = 0, inbegin = 0, inend = inorder.size();
        TreeNode* root = _build(preorder, inorder, prei, 0, (int)inorder.size() - 1);
        //注意这里的prei是引用
        return root; //最后返回的就是根结点
    }
};

中序后序序列构建二叉树

题目

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

思路

大体思路和上一道题一样,只不过是用后序找根

  • 而且要注意后序是左右根的顺序
  • 后序序列往左走,先找的是右子树的根,所以要先去中序的右半范围寻找

代码 

TreeNode* _build(vector<int>& inorder, vector<int>& postorder,int& posti,int inbegin,int inend){
        if(inbegin>inend){
            return nullptr;
        }
        TreeNode* root=new TreeNode(postorder[posti]);
        int begin=inbegin;
        while(begin<=inend){
            if(postorder[posti]==inorder[begin]){
                break;
            }
            begin++;
        }
        --posti;  //注意是--嗷
        root->right=_build(inorder,postorder,posti,begin+1,inend);//先被构建的是右子树
        root->left=_build(inorder,postorder,posti,inbegin,begin-1);
        return root;
}
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int posti=postorder.size()-1;
        TreeNode* root=_build(inorder,postorder,posti,0,inorder.size()-1);
        return  root;
    }

非递归前序遍历

题目

144. 二叉树的前序遍历 - 力扣(LeetCode)

思路

虽然是非递归,但思路和递归一样,只不过得用循环做

  • 前序是根左右
  • 也就是说,前序的前几个元素都是左方结点
  • 之后开始访问右子树(按照左方节点的倒序)
  • 所以,倒序 -- 不就是栈吗,先入后出!!
  • 所以要用一个栈来存放左结点,然后不断取栈顶+访问其右子树

代码

vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;  //存放前序序列的结点值
        stack<TreeNode*> tmp; //存放左结点指针,用于访问其右子树
        TreeNode* cur=root;
        while(!tmp.empty()||cur){ //栈空+当前结点是空结点,出循环
            while(cur){
                ans.push_back(cur->val); //入左结点
                tmp.push(cur); //存左结点指针
                cur=cur->left;
            }
            TreeNode* top=tmp.top(); 
            cur=top->right; //访问右子树
            tmp.pop();
        }
        return ans;
    }

非递归中序遍历

题目

94. 二叉树的中序遍历 - 力扣(LeetCode)

思路

和前序的思路差不多,但是要注意,中序是左根右

  • 所以需要将左结点先存入栈中(先不要访问!!!)
  • 然后按照栈中顺序,取出,访问
  • 然后,将拿出结点的右结点按照上述操作重复进行,直到遍历完整棵树

代码

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;  //存放中序序列的结点值
        stack<TreeNode*> tmp; //存放左结点指针,用于访问其右子树
        TreeNode* cur=root;
        while(!tmp.empty()||cur){ //栈空+当前结点是空结点,出循环
            while(cur){ //把所有左结点入栈
                tmp.push(cur);
                cur=cur->left;
            }
            cur=tmp.top();  //拿取栈顶元素
            ans.push_back(cur->val); //入中序序列

            cur=cur->right; //访问其右子树
            tmp.pop(); //该结点没啥用了,直接出
        }
        return ans;
    }

非递归后序遍历

题目

145. 二叉树的后序遍历 - 力扣(LeetCode)

思路

  • 后序是左右根
  • 也就是说,要先遍历完左结点,以及右结点后,才能访问结点
  • 而且要注意,如果该结点有右子树,就需要先循环右子树
  • 但是当循环完成后需要返回来访问结点
  • 该如何判断此时到底该访问右子树还是访问自己呢?
  • 想一想,当上一个结点是其右结点时,不就是该访问自己的时候吗?
  • 而上一个结点是左结点时,是该访问右子树的时候了
  • 所以需要拿到父结点,然后进行判断

代码

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;  //存放中序序列的结点值
        stack<TreeNode*> tmp; //存放左结点指针,用于访问其右子树
        TreeNode* cur=root,*prev=nullptr; 
        while(!tmp.empty()||cur){ //栈空+当前结点是空结点,出循环
            while(cur){ //把所有左结点入栈(cur就用于把当前结点的左结点存入栈)
                tmp.push(cur);              
                cur=cur->left;
            }
            TreeNode* top=tmp.top(); //代表当前的根结点
            if(top->right==nullptr || top->right==prev){
                ans.push_back(top->val); //入后序序列
                prev=top; //用于下一次循环时,判断上一个结点的右子树是否被访问过
                tmp.pop(); //该结点没啥用了,直接出
            }
            else{
                cur=top->right; //没有被访问过,就去右子树进行循环
            }
        }
        return ans;
    }

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

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

相关文章

3D模型格式转换工具HOOPS Exchange协助Epic Games实现CAD数据轻松导入虚幻引擎

一、面临的挑战 Epic Games最为人所知的身份可能是广受欢迎的在线视频游戏Fortnite的开发商&#xff0c;但它也是虚幻引擎背后的团队&#xff0c;虚幻引擎是一种实时3D创作工具&#xff0c;为世界领先的游戏提供动力&#xff0c;并且也被电影电视、建筑、汽车、制造、模拟等领…

数据结构——看完这篇保证你学会队列

数据结构——队列 一、队列的概念二、队列的实现方式三、队列所需要的接口四、接口的详细实现4.1初始化4.2销毁4.3入队4.5出队4.6获取队头元素4.7获取队尾元素4.8获取队列元素个数4.9判空 五、完整代码5.1Queue.h5.2Queue.c5.3test.c 一、队列的概念 队列&#xff1a;只允许在…

安防监控系统/视频云存储EasyCVR平台视频无法播放是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Android studio 断点调试、日志断点

目录 参考文章参考文章1、运行调试2、调试操作3、断点类型行断点的使用场景属性断点的使用场景异常断点的使用场景方法断点的使用场景条件断点日志断点 4、断点管理区 参考文章 参考文章 1、运行调试 开启 Debug 调试模式有两种方式&#xff1a; Debug Run&#xff1a;直接…

Windows系统如何设置Cpolar内网穿透为后台服务并实现开机自启动?

文章目录 Windows用户如何将cpolar内网穿透配置成后台服务&#xff0c;并开机自启动&#xff1f;前置准备&#xff1a;VS Code下载后&#xff0c;默认安装即可VS CODE切换成中文语言 1. 将隧道参数保存到配置文件1.1 编辑配置文件1.2 启动配置中的隧道 2 将cpolar安装为服务开机…

vue应用全局音乐(自动播放)

这里写自定义目录标题 1.从同事哪里白嫖过来的&#xff0c;主要是jq写的&#xff0c;需要单独引入jq cdn 2.打开index.html 将代码放到里面 <!DOCTYPE html> <html><head><meta charset"utf-8" /><metaname"viewport"content…

【C++杂货铺】优先级队列的使用指南与模拟实现

文章目录 一、priority_queue的介绍二、priority_queue的使用2.1 数组中的第k个最大元素 三、priority_queue模拟实现3.1 仿函数3.2 成员变量3.3 成员函数3.3.1 构造函数3.3.2 AdjustDown3.3.3 push3.3.4 AdjustUp3.3.5 pop3.3.6 empty3.3.7 size 四、结语 一、priority_queue的…

浅谈应急照明及疏散指示系统在建筑物消防的应用

安科瑞 华楠 摘要&#xff1a;智能消防应急照明及琉散指示系统是建筑物消防系统中的重要内容,为人们的生命安全提供了重要的安全保证。该文在研究中主要以智能消防应急照明及琉散指示系统为要点,探究智能消防应急照明及琉散指示系统的特征,其核心目的是优化智能消防应急照明及…

无代码编程时代的到来:新兴工具和平台的前瞻展望

目录 一、无代码编程的概念和意义 二、新兴工具和平台的前瞻展望 2.1 低代码/无代码开发平台&#xff1a; 2.2 人工智能应用开发工具&#xff1a; 2.3 数据分析和可视化工具&#xff1a; 三、未来发展前景和挑战 随着技术的不断进步和发展&#xff0c;传统的编程模式面临…

2023,DaaS驶入“AI大航海时代”

2023&#xff0c;“制胜”已然成为所有行业、企业的共同命题&#xff0c;随着数字化行至中程&#xff0c;数据壁垒逐渐被打破&#xff0c;DaaS作为企业增长问题的解法&#xff0c;再次被看到。 作者|斗斗 编辑|皮爷 出品|产业家 2002年&#xff0c;在竞争激烈的美国职业…

掌握时间复杂度, 编写高效代码

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:巧用抽象类与接口&#xff0c;打造高效Java程序(下)&#x1f649; &#x1f439;今日诗词:昨夜星辰昨夜风&#xff0c;画楼西畔桂堂东&#x1f439; ⛳️点赞 ☀️…

【多线程】线程池 详解

线程池 详解 1. 线程池是什么2. 标准库中的线程池3. 实现线程池4. 面试题 1. 线程池是什么 虽然线程的创建和销毁的开销比较小, 但还是有的, 如果频繁的创建和销毁线程, 开销还是比较大的.解决: 线程池或者协程, 本文主讲线程池. 线程池: 把线程提前创建好, 放到池子里, 后面…

黑马 小兔鲜儿 uniapp 小程序开发- 分类模块- day04

黑马 小兔鲜儿 uniapp 小程序开发- 推荐模块- day03_软工菜鸡的博客-CSDN博客 本课程是全网首套用 vue3 加 TS 写的 uniapp 项目&#xff0c; 里面大量封装自己的组件库&#xff0c;课程从 uni-app 基础入手&#xff0c;按照9大电商业务模块逐步实现完整的电商购物流程业务&am…

技术人员应该使用那种搜索引擎?

built-in.o是Linux内核中的组件 下面是三种主流搜索引擎的搜索结果&#xff0c;请参考&#xff0c;一切尽在不言中。

CSS - 快速实现悬浮吸顶,当页面滑动一定距离时固定吸附在顶部(position: sticky)

效果图 如下图所示&#xff0c;利用 position: sticky 属性轻松实现。 示例代码 新建一个 *.html 文件&#xff0c;一键复制运行起来。 <body><section class"content"><div class"item">我是悬浮吸顶区域</div><h1>我是…

【leetcode 力扣刷题】栈和队列的基础知识 + 栈的经典应用—匹配

栈和队列的基础知识 栈的经典应用—匹配 栈和队列基础知识232. 用栈实现队列225. 用队列实现栈 20. 有效的括号1047. 删除字符串中的所有相邻重复项 栈和队列基础知识 数据结构课程介绍线性结构的时候&#xff0c;介绍有线性表、链表、栈和队列。线性表&#xff0c;比如array…

室内探索无人机,解决复杂环境下的任务挑战!

前言 室内探索无人机是一种专为在室内环境中进行任务的无人机系统。相比传统的人员部署&#xff0c;室内探索无人机具有更高的灵活性和机动性&#xff0c;能够在复杂的室内环境中执行任务&#xff0c;用于未知环境的探索和特定目标的搜索。 为完成无人机室内搜索与识别等复杂…

无缝数据传输:StreamSet安装部署的最佳实践

文章目录 概要安装方法尝试安装部署方案1. 下载datacollector镜像2. 启动容器3. 访问streamsets小结 概要 StreamSets是一款流数据集成平台&#xff0c;旨在帮助用户轻松地收集、处理和传输大规模数据流。它提供了直观的界面和强大的功能&#xff0c;可用于实时数据流的提取、…

无线测温系统在运行时怎么判断配电设备出现故障?

现如今&#xff0c;电力测温方面使用无线测温系统的使用范围越来越大&#xff0c;比较熟悉的人都了解&#xff0c;传统的温度测量方式周期长、施工复杂&#xff0c;效率低&#xff0c;不便于管理&#xff0c;发生故障时要耗费大量的人力物理排查和重新铺设线缆。而在特定场合下…

若依+lodop+jasperreports+ireport 设计打印票据格式(一)

若依lodopjasperreportsireport 设计打印票据格式 一、设计表格 官网下载ireport5.6&#xff0c;解压安装&#xff0c;jdk 1.7适配ireport5.6&#xff0c;jdk1.8不适配 安装好jdk1.7(不用配置path&#xff0c;安装好就行)&#xff0c;进入ireport5.6安装目录&#xff0c;找到…