6.二叉树.题目3

6.二叉树.题目3

  • 题目
    • 17.二叉搜索树中的众数
    • 18.二叉树的最近公共祖先
    • 19.二叉树搜索树的最近公共祖先
    • 20.二叉搜索树中的插入操作。
      • 普通二叉树的删除方式
    • 21.删除二叉搜索树中的节点
    • 22.修剪二叉树
    • 23.将有序数组转化为二叉搜索树
    • 24.把二叉搜索树转化为累加树
  • 总结

题目

17.二叉搜索树中的众数

(题目链接)
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素。
首先如果不是二叉搜索树的话,应该怎么解题,是二叉搜索树,又应该如何解题,两种方式做一个比较,可以加深大家对二叉树的理解。

  • 如果不是二叉搜索树:最直观的办法是先将树遍历一遍,再使用map统计频率,然后把频率排序,最后取高频的元素的集合
/* 表示在排序时,a应该排在b前面。因此,当我们使用这个函数对vector<pair<int, int>>进行排序时,频率最高的元素会被放在前面*/
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {return a.second > b.second;
}vector<int> findMode(TreeNode* root) {unordered_map<int, int> map; // key:元素,value:出现频率vector<int> result;if (root == NULL) return result;// 遍历树,并将树出现频率统计在map中searchBST(root, map);vector<pair<int, int>> vec(map.begin(), map.end());sort(vec.begin(), vec.end(), cmp); // 给频率排个序result.push_back(vec[0].first);for (int i = 1; i < vec.size(); i++) {// 取最高的放到result数组中if (vec[i].second == vec[0].second) result.push_back(vec[i].first);else break;}return result;}
  • 如果是二叉搜索树:将二叉搜索树通过中序遍历展开为递增序列,然后通过一次遍历即可把众数放入数组res中。使用了pre指针和cur指针的技巧。使用一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
private:int maxcount = 0;int count = 0;TreeNode* pre = nullptr;std::vector<int> res;void backtracking(TreeNode* root){if(root==nullptr) return;backtracking(root->left);// 根据pre,root修改计数countif(pre==nullptr) count=1;else if(pre->val==root->val) count++;else count=1; pre = root; // 更新pre的指针位置if(count==maxcount) res.push_back(root->val);if(count>maxcount){res.clear();maxcount = count;res.push_back(root->val);}backtracking(root->right);return;}
public:vector<int> findMode(TreeNode* root) {res.clear();backtracking(root);return res;}

18.二叉树的最近公共祖先

(题目链接)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。最近公共祖先的定义:对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。说明:所有节点的值都是唯一的;p、q 为不同节点且均存在于给定的二叉树中。
寻找最近公共祖先节点,需要我们从二叉树的底部往顶部进行处理,这就需要使用后序遍历的方法(左右上)
终止条件:当cur==nullptr,以及当cur==p || q说明也遍历到了目标节点,到达叶子节点位置时结束
确定单层递归逻辑-本题的递归函数有返回值,因为回溯的过程需要递归函数的返回值做判断,而递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值。

// 搜索一条边
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
// 搜索整个树
left = 递归函数(root->left);  // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理;         // 中 

在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回;如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)
在这里插入图片描述

    TreeNode* backtracking(TreeNode* root, TreeNode* p, TreeNode* q){// 终止条件if(root==nullptr) return root;if(root==p || root==q) return root;TreeNode* left = backtracking(root->left, p, q);TreeNode* right = backtracking(root->right, p, q);// 后序遍历,中序需要左,右子树的值进行判断if(left!=nullptr && right!=nullptr) return root;else if(left==nullptr && right!=nullptr) return right;else if(left!=nullptr && right==nullptr) return left;else return nullptr;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {return backtracking(root, p, q);}

19.二叉树搜索树的最近公共祖先

(题目链接)
因为是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先,那么该如何判断是最近祖先?
在这里插入图片描述
跟据以上的例子,所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[q, p]区间中,那么cur就是 q和p的最近公共祖先。

    TreeNode* backtracking(TreeNode* root, TreeNode* p, TreeNode* q){if(root==nullptr) return root;if(root->val>p->val && root->val>q->val){TreeNode* left = backtracking(root->left, p, q);if(left!=nullptr) return left;}if(root->val<p->val && root->val<q->val){TreeNode* right = backtracking(root->right, p, q);if(right!=nullptr) return right;}return root;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {return backtracking(root, p, q);}

20.二叉搜索树中的插入操作。

(题目链接)
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证:新值和原始二叉搜索树中的任意节点值都不同。其实只要遍历二叉搜索树,找到空节点 插入元素就可以了,那么这道题其实就简单了。
递归函数设置返回值,可以利用返回值完成新加入的节点与其父节点的赋值操作
终止条件:当cur==nullptr,到达叶子节点位置时结束,就是要插入新节点的位置,并把插入的节点返回。
单层递归的逻辑:二叉搜索树的递归方向,根据val值与root->val的比值大小

	// 递归函数设置返回值,用于终止条件时新节点的赋值TreeNode* insertIntoBST(TreeNode* root, int val) {if(root==nullptr){TreeNode* node = new TreeNode(val);return node;}if(val>root->val) root->right = insertIntoBST(root->right, val); //在终止条件时完成对root的插入新子节点if(val<root->val) root->left = insertIntoBST(root->left, val);return root;}

当然该递归函数也可不设置返回值,需要记录每次递归前上一个节点(parent),遇到空节点了,就让parent左孩子或者右孩子指向新插入的节点。然后结束递归。

	// 递归函数不设置返回值,需要外部指针记录父节点TreeNode* par;void backtracking(TreeNode* root, int val){if(root==nullptr){TreeNode* node = new TreeNode(val);if(val>par->val) par->right = node;else par->left = node;return;}par = root; //不需要回溯,只需记录每次递归的父节点if(val>root->val) backtracking(root->right, val);if(val<root->val) backtracking(root->left, val);return;}TreeNode* insertIntoBST(TreeNode* root, int val) {par = new TreeNode(val);if(root==nullptr){root = new TreeNode(val);}backtracking(root, val);return root;}	

普通二叉树的删除方式

对于没有数值大小排序需要的普通二叉树,通用的二叉树的删除方法。主要分为两个步骤

  1. 和目标节点的右子树最左面节点交换
  2. 直接用nullptr覆盖
	// 比较绕,需要思考以西。TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) return root;if (root->val == key) {if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用return root->left;}TreeNode *cur = root->right;while (cur->left) {cur = cur->left;}swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。}root->left = deleteNode(root->left, key);root->right = deleteNode(root->right, key);return root;}

21.删除二叉搜索树中的节点

(题目链接)
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。删除节点分为两个步骤:1.找到要删除的节点 2.删除元素。但搜索二叉树的删除节点要比普通的二叉树复杂多,因此涉及要删除节点的子树的重新排序的问题
递归函数参数,返回值:
终止条件:当cur==nullptr,到达叶子节点位置时结束
每层递归逻辑:得分情况处理(处理是在父节点parent层处理的)

  • 根据key,没有找到要删除的节点,不用处理,遍历到空节点就返回了
  • 左右子节点都为空,则直接删除该节点
  • 左节点为空,右节点非空,删除该节点,右节点补位
  • 右节点为空,左节点非空,删除该节点,左节点补位
  • 左右节点为非空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。(这样操作的目的是二叉搜索树的右子树的元素是均大于左子树的元素的,而右子树的的最小值必在最深的左叶子节点处,因此若删除父节点,则可以很自然地把左子树的头节点接到右子树的最深左叶子节点的left端)

在这里插入图片描述

    TreeNode* deleteNode(TreeNode* root, int key) {if(root==nullptr) return root;// 处理节点 因为递归函数有返回值-实现父节点修改处理if(root->val == key){if(root->left==nullptr && root->right==nullptr){delete root;return nullptr;}else if(root->left==nullptr && root->right!=nullptr){auto node = root->right;delete root;return node;}else if(root->right==nullptr && root->left!=nullptr){auto node = root->left;delete root;return node;}else{auto node = root->right;while(node->left!=nullptr) node=node->left;node->left = root->left;TreeNode* tmp = root;root = root->right;delete tmp; //释放原本root的内存return root;}}//指定递归的方向if(root->val>key) root->left = deleteNode(root->left, key);if(root->val<key) root->right = deleteNode(root->right, key);return root;}

22.修剪二叉树

(题目链接)
给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L)。可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
递归函数确定参数,返回值:有返回值,更方便,可以通过递归函数的返回值来移除节点(返回值是为了这一点服务的);
终止条件:当root==nullptr
单层递归的逻辑:1. (本层)如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点;如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。2.(下一层)接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right

    TreeNode* trimBST(TreeNode* root, int low, int high) {if(root==nullptr) return nullptr;// 处理节点,相当于局部调整root节点附近if(root->val<low){TreeNode* right = trimBST(root->right, low, high);return right;}if(root->val>high){TreeNode* left = trimBST(root->left, low, high);return left;}root->left = trimBST(root->left, low, high);root->right = trimBST(root->right, low, high);return root;}

23.将有序数组转化为二叉搜索树

(题目链接)
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。高度平衡二叉树指:一个高度平衡二叉树是指一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1
在这里插入图片描述
做这道题目需要了解:1.从中序后序遍历序列构造二叉树,最大二叉树,二叉搜索树中的插入操作;删除二叉搜索数中的节点。其实数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取。
本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间。但数组长度为偶数,中间节点有两个,取不同的作为根节点,会造成不同的平衡二叉搜索树。
在这里插入图片描述
递归函数参数和返回值:首先是传入数组,然后就是左下标left和右下标right;在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组,此处定义使用的是左闭右闭区间
递归终止条件:当区间left > right的时候,就是空节点了
确定单层递归逻辑:所以可以这么写:int mid = left + ((right - left) / 2);出于考虑right+left可能会出现越界的问题。取了中间位置,就开始以中间位置的元素构造节点,然后接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点。最后返回root
递归法

    TreeNode* traversal(std::vector<int>& nums, int left, int right){// 切割问题,根据区间下标返回if(left>right) return nullptr;int mid = left + (right-left)/2;TreeNode* root = new TreeNode(nums[mid]);root->left = traversal(nums, left, mid-1);root->right = traversal(nums, mid+1, right);return root; }TreeNode* sortedArrayToBST(vector<int>& nums) {return traversal(nums, 0, nums.size()-1);}

不断中间分割,然后递归处理左区间,右区间,也可以说是分治;这需要应该对通过递归函数的返回值来增删二叉树很熟悉了,这也是常规操作。


24.把二叉搜索树转化为累加树

(题目链接)
给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree)使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。提示:树中的节点数介于 0 和 104 之间;每个节点的值介于 -104 和 104 之间;树中的所有值 互不相同;给定的树为二叉搜索树。
在这里插入图片描述
从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加——采用pre指针的题目还有1.搜索树的最小绝对值差,2.我的众数
递归函数参数和返回值:不需要返回值,只需要使用pre记录前一个节点的val作累加
终止条件:当cur==nullptr即可终止
单层递归的逻辑:遵循右中左遍历顺序,中间节点的操作是赋值为当前值+pre的值,并且更新pre值
递归法

    int pre=0;void traversal(TreeNode* root){if(root==nullptr) return;traversal(root->right);root->val += pre;pre = root->val;traversal(root->left);}TreeNode* convertBST(TreeNode* root) {pre = 0;traversal(root);return root;}

迭代法-深度优先


总结

求最小公共祖先:

  1. 需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
  2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断
  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果

添加,删除二叉树节点:

  1. 二叉搜索树删除节点比增加节点复杂的多,二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。
  2. 删除节点,最关键的部分是处理该节点左右节点都存在的情况。

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

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

相关文章

LLM大模型本地部署与预训练微调

以通义千问-1_8B-Chat为例&#xff0c;按照官方教程&#xff0c;简单介绍如何将模型进行本地CPU部署以及预训练微调&#xff1a; 1、环境条件&#xff1a;Linux 24G内存左右 2、本地部署&#xff1a; 提前安装好git跟git lfs&#xff0c;否则可能拉取不到模型文件&#xff0c;g…

【教程】DPW 325T FPGA板卡程序下载与固化全攻略

到底什么是固化&#xff1f;&#xff1f;&#xff1f; 在开发板领域&#xff0c;"固化"通常指的是将软件或操作系统的镜像文件烧录&#xff08;Flash&#xff09;到开发板的存储介质上&#xff0c;使其成为开发板启动时加载的系统。这个过程可以确保开发板在启动时能…

从单点到全景:视频汇聚/安防监控EasyCVR全景视频监控技术的演进之路

在当今日新月异的科技浪潮中&#xff0c;安防监控领域的技术发展日新月异&#xff0c;全景摄像机便是这一领域的杰出代表。它以其独特的360度无死角监控能力&#xff0c;为各行各业提供了前所未有的安全保障&#xff0c;成为现代安防体系中的重要组成部分。 一、全景摄像机的技…

【SpringMVC】第1-7章

第1章 初始SpringMVC 1.1 学习本套教程前的知识储备 JavaSEHTMLCSSJavaScriptVueAJAX axiosThymeleafServletMavenSpring 1.2 什么是MVC MVC架构模式相关课程&#xff0c;在老杜的JavaWeb课程中已经详细的讲解了&#xff0c;如果没有学过的&#xff0c;可以看这个视频&…

当下为什么越来越多的企业选择产业园区,而不是写字楼?

随着经济形势的变化和各行业的升级转型&#xff0c;我们发现越来越多的企业选择扎根产业园而非传统的写字楼&#xff0c;这一现象背后真正的原因是什么呢&#xff1f;今天我们就站在企业自身发展的角度&#xff0c;全维度对比一下产业园与传统写字楼的区别&#xff0c;看是否能…

参加六西格玛绿带培训是投资未来,还是花冤枉钱?

是否值得花费资金参加六西格玛绿带培训&#xff0c;取决于多个因素。 从积极的方面来看&#xff0c;参加六西格玛绿带培训具有以下潜在价值&#xff1a; 1. 提升专业技能&#xff1a;使您掌握一套系统的问题解决方法和流程改进工具&#xff0c;有助于在工作中更高效地解决复杂…

代码随想录算法训练营:14/60

非科班学习算法day14 | LeetCode266:翻转二叉树 &#xff0c;Leetcode101: 对称二叉树&#xff0c;Leetcode100:相同的的树 &#xff0c;LeetCode572:另一颗树的子树&#xff0c;LeetCode104&#xff1a;二叉树的最大深度&#xff0c;LeetCode559&#xff1a;N叉树的最大深度 目…

SDK 程序卡在 AXI DMA 配置 (XAxiDma_CfgInitialize)的解决方法

在进行 AXI DMA 配置 时候代码一直卡在XAxiDma_CfgInitialize函数出不来&#xff0c;也没有打印报错。 /* Initialize the XAxiDma device.*/CfgPtr XAxiDma_LookupConfig(DeviceId);if (!CfgPtr) {xil_printf("No config found for %d\r\n", DeviceId);return XST_…

ResNet-50算法

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、理论知识储备 1.CNN算法发展 AlexNet是2012年ImageNet竞赛中&#xff0c;由Alex Krizhevsky和Ilya Sutskever提出&#xff0c;在2012年ImageNet竞赛中&a…

头歌——机器学习——支持向量机案例

第1关&#xff1a;基于支持向量机模型的应用案例 任务描述 本关任务&#xff1a;编写一个基于支持向量机模型的应用案例。 相关知识 在本应用案例中&#xff0c;我们借助一个具体的实际问题&#xff0c;来完整地实现基于支持向量机模型的开发应用。在此训练中&#xff0c;我…

运筹系列93:VRP精确算法

1. MTZ模型 MTZ是Miller-Tucker-Zemlin inequalities的缩写。除了定义是否用到边 x i j x_{ij} xij​外&#xff0c;还需要定义一个 u i u_i ui​用来表示此时车辆的当前载货量。注意这里x变量需要定义为有向。 这里定义为pickup问题&#xff0c;代码为&#xff1a; using Ju…

windows下载jdk并安装步骤(保姆级教程)

一、下载jdk 下载地址: https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 二、双击下载好的jdk 更改安装目录然后点击下一步 然后会弹出jre的安装&#xff0c;需要选择路径&#xff08;注意&#xff1a;这里的路径必须跟前面的jdk在…

将huggingface的大模型转换为safetensor格式

很多huggingface的大语言模型都是pytorch的格式&#xff0c;但是mindie需要safetensor格式&#xff0c;另外mindieservice加载原始的baichuan2-13b的模型出错&#xff0c;后来排查是bfloat16数据格式的问题&#xff0c;所以这次转换要一次性转为float16的格式。 上代码&#x…

计算机网络:如何隐藏真实的IP和MAC地址?

目录 一、什么是MAC地址二、什么是IP地址三、如何隐藏真实的MAC地址四、如何隐藏真实的IP地址 一、什么是MAC地址 MAC地址&#xff0c;全称为媒体访问控制地址&#xff08;Media Access Control Address&#xff09;&#xff0c;是一种用于网络通信的唯一标识符。它是由IEEE 8…

PLC网关如何选择?plc网关作用-天拓四方

一、PLC网关在工业自动化领域的重要性和作用 PLC网关在工业自动化领域的重要性和作用不言而喻。作为工业自动化系统的重要组成部分&#xff0c;PLC网关起到了关键的桥梁作用&#xff0c;实现了PLC与其他设备、系统之间的数据传输和通信。 首先&#xff0c;PLC网关的重要性体现…

最像人声的语音合成模型-ChatTTS

目录 写在前面 一、使用ChatTTS 二、优点 三、局限 写在前面 最像人声的AI来了&#xff01;语音开源天花板ChatTTS火速出圈&#xff0c;3天就斩获9k个star。截至发稿前&#xff0c;已经25.9k个star了。这是专门为对话场景设计的语音生成模型&#xff0c;用于LLM助手对话任务…

搭建抖音微短剧系统:源码部署与巨量广告回传全解析

在数字化浪潮中&#xff0c;抖音微短剧已成为内容创作的新宠。想要搭建一个高效的抖音微短剧系统&#xff0c;并实现与巨量广告的有效回传吗&#xff1f;本文将为您详细解析源码部署与广告回传的关键步骤。 一、源码部署&#xff1a;构建短剧系统的基石 源码是软件开发的起点…

vscode远程连接Ubantu

一、首先用VM虚拟机打开一个Linux系统 二、打开VScode 在扩展里安装 安装后&#xff0c;打开Linux查看IP地址 在VScode 中新建连接主机 输入linux_nameip地址 -A 然后输入Linux的登录密码 就可以远程操控 Linux了 可以在终端中远程控制Linux 点击左上角的打开文件夹可以很…

什么是 Azure OpenAI?

目录 一、说明 二、什么是 Azure OpenAI 2.1 网络结构 2.2 、为什么使用 Azure OpenAI 2.3 如何使用 Azure OpenAI 三、从哪里开始 Azure OpenAI 之旅 3.1 关于 Azure OpenAI&#xff0c;我还需要了解什么 3.2 RBAC 权限和角色 3.3 演示 1&#xff1a;在公共数据上应用…

聚合项目学习

首先建立一个总的工程目录&#xff0c;里边后期会有我们的父工程、基础工程(继承父工程)、业务工程&#xff08;依赖基础工程&#xff09;等模块 1、在总工程目录中&#xff08;open一个空的文件夹&#xff09;&#xff0c;首先建立一个父工程模块&#xff08;通过spring init…