【LeetCode刷题笔记(6-2)】【Python】【三数之和】【双指针】【中等】
文章目录
- 引言
- 三数之和
- 题目描述
- 示例
- 示例1
- 示例2
- 示例3
- 提示
- 解决方案3:【双指针】
- 结束语
三数之和
引言
编写通过所有测试案例的代码并不简单,通常需要深思熟虑和理性分析。虽然这些代码能够通过所有的测试案例,但如果不了解代码背后的思考过程,那么这些代码可能并不容易被理解和接受。我编写刷题笔记的初衷,是希望能够与读者们分享一个完整的代码是如何在逐步的理性思考下形成的。我非常欢迎读者的批评和指正,因为我知道我的观点可能并不完全正确,您的反馈将帮助我不断进步。如果我的笔记能给您带来哪怕是一点点的启示,我也会感到非常荣幸。同时,我也希望我的分享能够激发您的灵感和思考,让我们一起在编程的道路上不断前行~
三数之和
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。
请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例
示例1
- 输入:nums = [-1,0,1,2,-1,-4]
- 输出:[[-1,-1,2],[-1,0,1]]
示例2
- 输入:nums = [0,1,1]
- 输出:[]
- 解释:唯一可能的三元组和不为 0 。
示例3
- 输入:nums = [0,0,0]
- 输出:[[0,0,0]]
- 解释:唯一可能的三元组和为 0 。
提示
- 3 <= nums.length <= 3000
- -105 <= nums[i] <= 105
解决方案3:【双指针】
在博客【LeetCode刷题笔记(6-1)】【Python】【三数之和】【哈希表】【中等】中,我们详尽地探讨了如何利用【哈希表】精妙设计算法,从而顺利通关【三数之和】这一挑战。
在此过程中,我们深度挖掘了问题细节,确保算法能够通过所有的测试案例(基于哈希表设计的算法在【三数之和】上很容易超时) —> 这不仅是一次技术上的磨练,更是对细心与耐心的极致考验。
问题1:为什么【三数之和】问题可以用【双指针】来解决?
在博客【LeetCode刷题笔记(6-1)】【Python】【三数之和】【哈希表】【中等】中,我们从两数之和的解决方案中汲取灵感,通过举一反三,巧妙地运用哈希表来解决【三数之和】问题。而对于【双指针】的使用,背后又隐藏着怎样的深刻逻辑呢?—> 先回到三数之和的本质:查找目标值。
在【哈希表】解决方案中,我们通过两层遍历的方式,首先固定两个值,然后利用哈希表快速查找剩下的一个值。那么,除了哈希表外,是否还有其他算法可以达到类似的效果呢?答案是肯定的,那就是双指针方法。
通过固定一个值,然后使用双指针在数组中查找剩余的两个值,同样可以达到解决问题的目的。这种方法的优势在于,它避免了哈希表的开销,同时保持了查找的效率。
我们可以把双指针分为左指针left
和右指针right
。在循环遍历数组nums
的每个元素nums[i]
时,设计算法找到正确的nums[left]
和nums[right]
, 共同组成满足题意的三元组[nums[i], nums[left], nums[right]]
。
代码的初始版本如下:
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: # 如果输入的数组为空或长度小于3,直接返回空列表 if not nums or len(nums) < 3: return [] # 对输入的数组进行排序nums.sort() # 获取列表的长度 n = len(nums) # 初始化结果列表 result_list = [] # 遍历数组中的每个元素 --> 固定元素nums[i]for i in range(n): # 初始化左指针和右指针,分别指向剩余两个元素的位置 left = 0 right = n - 1 # 当左指针小于右指针时,进行循环 while left < right: # 如果三个元素的和等于0,则将这三个元素添加到结果列表中,并同时移动左指针和右指针if nums[i] + nums[left] + nums[right] == 0: result_list.append([nums[i], nums[left], nums[right]]) left += 1 # 左指针右移right -= 1 # 右指针左移 # 如果三个元素的和大于0,则右指针向左移动,以减少和的值 elif nums[i] + nums[left] + nums[right] > 0: right -= 1 else: # 如果三个元素的和小于0,则左指针向右移动,以增加和的值 left += 1 return result_list # 返回结果列表
初步运行结果
可以看到,虽然结果列表中,包含了正确的三元组,但更多的是重复的三元组(如上图的蓝色框部分),甚至还出现重复利用数组元素的三元组(上图红色框,重复利用元素2)。
尽管初始的代码版本问题很大,但还是有一些小细节值得拎出来说明一下:
细节1:为什么要对数组nums进行排序?
如果您已看完这篇博客,您会知道其中一个原因是为了方便去重。但目前的【初始代码】压根与去重无关。其实,现阶段对数组nums
进行排序的原因是为了方便确定左指针left
和右指针right
的移动方向。
- 如果数组
nums
是乱序的,如果nums[i] + nums[left] + nums[right] > 0
,下一步是移动左指针还是右指针?显然是不确定的。 - 如果数组
nums
是有序的,比如说从小到大排列。那么left
右移 ==> 三元组之和将增加;同理,right
左移 ==> 三元组之和将减少;那么nums[i] + nums[left] + nums[right] > 0
,下一步自然是right
左移,使三元组之和进一步接近0。
从细节1的分析中我们可以看出,对数组nums
进行排序对于应用双指针算法解决三数之和问题至关重要。甚至可以说,方便去重只是这个过程中的一个额外的好处,就像“买一送一”一样。
细节2:while left < right:
这个语句能否取等,即while left <= right:
?
不能。我们可以从左右指针的定义中找到答案 ==> 分别指向剩余两个元素的位置。如果left == right
成立 ⇒ 三元组会包含同一元素!
细节3:当三元组元素满足nums[i] + nums[left] + nums[right] == 0
时,为什么下一步要同时进行left
右移和right
左移?
- 如果我们只进行
left
右移,相当于nums[i]
和nums[left]
是固定的。想要nums[i] + nums[left] + nums[right] == 0
继续成立,nums[right]即使元素位置变了,但元素值不可能变 ==> 只移动一个指针没有意义,都不移动更没有意义 —> 同时进行left
右移和right
左移。
想清楚三个细节后,紧接着就要思考为什么这个代码会生成这么多重复的三元组,甚至还出现重复利用数组元素的三元组?
问题1:左指针left
不应该初始化为0
如果我们把左指针left
初始化为0 ⇒ 在【双指针】查找剩余两个元素时,左指针left
的右移和右指针right
的左移都可能碰上事先固定的元素nums[i]
⇒ 出现重复利用数组元素的三元组。既然0不行,那么初始化为[0, i]的任何数都会出现这样的问题。
那将左指针left
初始化为i+1?
是的,这样既可以保证左指针left
的右移和右指针right
的左移不可能会碰到事先固定的元素nums[i]
,也不会出现遗漏某个三元组的情况。
代码如下(见修改1):
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: # 如果输入的数组为空或长度小于3,直接返回空列表 if not nums or len(nums) < 3: return [] # 对输入的数组进行排序nums.sort() # 获取列表的长度 n = len(nums) # 初始化结果列表 result_list = [] # 遍历数组中的每个元素 --> 固定元素nums[i]for i in range(n): # 初始化左指针和右指针, 分别指向剩余两个元素的位置 left = i + 1 # 修改1:初始化为i+1right = n - 1 # 当左指针小于右指针时,进行循环 while left < right: # 如果三个元素的和等于0,则将这三个元素添加到结果列表中,并同时移动左指针和右指针if nums[i] + nums[left] + nums[right] == 0: result_list.append([nums[i], nums[left], nums[right]]) left += 1 # 左指针右移right -= 1 # 右指针左移 # 如果三个元素的和大于0,则右指针向左移动,以减少和的值 elif nums[i] + nums[left] + nums[right] > 0: right -= 1 else: # 如果三个元素的和小于0,则左指针向右移动,以增加和的值 left += 1 return result_list # 返回结果列表
运行结果
可以看到,相比于上一版代码,这一版结果虽然仍然存在重复的三元组,但已经没有出现重复利用数组元素的三元组!<— 这是由于这版代码中, i < left < right
的关系是恒成立的,保证了不可能重复利用数组元素。
问题2:为什么仍然存在重复的三元组[-1, 0, 1]呢?
数组[-1,0,1,2,-1,-4]
排序后的结果为[-4, -1, -1, 0, 1, 2]
⇒ 当遍历数组元素,依次固定元素值时,元素-1
会连续两次作为固定的值 ⇒ 结果列表中多了一个三元组!!!
由于数组是有序的,我们可以很简单的对这种情况进行去重操作,代码如下(见修改2):
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: # 如果输入的数组为空或长度小于3,直接返回空列表 if not nums or len(nums) < 3: return [] # 对输入的数组进行排序nums.sort() print(nums)# 获取列表的长度 n = len(nums) # 初始化结果列表 result_list = [] # 遍历数组中的每个元素 --> 固定元素nums[i]for i in range(n):# 修改2:去除连续相同的元素值if i > 0 and nums[i] == nums[i-1]:continue# 初始化左指针和右指针, 分别指向剩余两个元素的位置 left = i + 1 # 修改1:初始化为i+1right = n - 1 # 当左指针小于右指针时,进行循环 while left < right: # 如果三个元素的和等于0,则将这三个元素添加到结果列表中,并同时移动左指针和右指针if nums[i] + nums[left] + nums[right] == 0: result_list.append([nums[i], nums[left], nums[right]]) left += 1 # 左指针右移right -= 1 # 右指针左移 # 如果三个元素的和大于0,则右指针向左移动,以减少和的值 elif nums[i] + nums[left] + nums[right] > 0: right -= 1 else: # 如果三个元素的和小于0,则左指针向右移动,以增加和的值 left += 1 return result_list # 返回结果列表
运行结果
可以看到,目前代码已经通过132个测试用例,但仍然会出现重复的三元组。
问题3:为什么仍然存在重复的三元组[-2, 0, 2]呢?,我们可以从排序后的数组[-2, 0, 0, 2, 2]
看出,0和2分别重复了两次 ⇒ 当我们固定元素-2时,并快速找到[-2, 0, 2]
这个正确的三元组,此时左指针left
右移,右指针right
左移。巧的是,左指针left
依然指向元素0,右指针依然指向元素2 ⇒ 出现重复的三元组[-2, 0, 2]。
因此我们在找到正确三元组的同时,应该把与左指针left
指向的元素相同的过滤掉,也把与右指针right
指向的元素相同的过滤掉。代码如下(见修改3):
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: # 如果输入的数组为空或长度小于3,直接返回空列表 if not nums or len(nums) < 3: return [] # 对输入的数组进行排序nums.sort() print(nums)# 获取列表的长度 n = len(nums) # 初始化结果列表 result_list = [] # 遍历数组中的每个元素 --> 固定元素nums[i]for i in range(n):# 修改2:去除连续相同的元素值if i > 0 and nums[i] == nums[i-1]:continue# 初始化左指针和右指针, 分别指向剩余两个元素的位置 left = i + 1 # 修改1:初始化为i+1right = n - 1 # 当左指针小于右指针时,进行循环 while left < right: # 如果三个元素的和等于0,则将这三个元素添加到结果列表中,并同时移动左指针和右指针if nums[i] + nums[left] + nums[right] == 0: result_list.append([nums[i], nums[left], nums[right]]) # 修改3while left < right and nums[left] == nums[left+1]: left += 1 # 如果左边的元素相等,则移动左指针 while left < right and nums[right] == nums[right-1]: right -= 1 # 如果右边的元素相等,则移动右指针 left += 1 # 左指针右移right -= 1 # 右指针左移 # 如果三个元素的和大于0,则右指针向左移动,以减少和的值 elif nums[i] + nums[left] + nums[right] > 0: right -= 1 else: # 如果三个元素的和小于0,则左指针向右移动,以增加和的值 left += 1 return result_list # 返回结果列表
运行结果
可以看到,目前代码已经能够通过所有测试案例,但代码运行时间显然还是比较长的。
问题4:还有没有进一步优化的可能性?
有的。
首先,数组nums
是排好序的,而我们的目标是找三个元素,让它们的和为0 ⇒ 如果当前固定的值为nums[i] > 0 ⇒ 根据i < left < right
, 即nums[i] < nums[i] < nums[i]
恒成立,不可能找到和为0的三元组,此时应该直接返回结果。
问题5:nums[i] = 0 的情况是否要保留,即返回条件是否可改为nums[i] >= 0
?
不可以,因为可能存在[0, 0, 0]
这个正确的三元组!代码如下(见修改4):
完整代码:
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: # 如果输入的数组为空或长度小于3,直接返回空列表 if not nums or len(nums) < 3: return [] # 对输入的数组进行排序nums.sort() # print(nums)# 获取列表的长度 n = len(nums) # 初始化结果列表 result_list = [] # 遍历数组中的每个元素 --> 固定元素nums[i]for i in range(n):# 修改2:去除连续相同的元素值if i > 0 and nums[i] == nums[i-1]:continue# 修改4 if nums[i] > 0:return result_list# 初始化左指针和右指针, 分别指向剩余两个元素的位置 left = i + 1 # 修改1:初始化为i+1right = n - 1 # 当左指针小于右指针时,进行循环 while left < right: # 如果三个元素的和等于0,则将这三个元素添加到结果列表中,并同时移动左指针和右指针if nums[i] + nums[left] + nums[right] == 0: result_list.append([nums[i], nums[left], nums[right]]) # 修改3while left < right and nums[left] == nums[left+1]: left += 1 # 如果左边的元素相等,则移动左指针 while left < right and nums[right] == nums[right-1]: right -= 1 # 如果右边的元素相等,则移动右指针 left += 1 # 左指针右移right -= 1 # 右指针左移 # 如果三个元素的和大于0,则右指针向左移动,以减少和的值 elif nums[i] + nums[left] + nums[right] > 0: right -= 1 else: # 如果三个元素的和小于0,则左指针向右移动,以增加和的值 left += 1 return result_list # 返回结果列表
运行结果:
复杂度分析
- 时间复杂度:O(N2),其中 N 是数组
nums
元素的数量。 - 空间复杂度:O(N)
- 存放数组排序后的新数组 ===> O(N)
结束语
- 亲爱的读者,感谢您花时间阅读我们的博客。我们非常重视您的反馈和意见,因此在这里鼓励您对我们的博客进行评论。
- 您的建议和看法对我们来说非常重要,这有助于我们更好地了解您的需求,并提供更高质量的内容和服务。
- 无论您是喜欢我们的博客还是对其有任何疑问或建议,我们都非常期待您的留言。让我们一起互动,共同进步!谢谢您的支持和参与!
- 我会坚持不懈地创作,并持续优化博文质量,为您提供更好的阅读体验。
- 谢谢您的阅读!
相关文章:

【LeetCode刷题笔记(6-2)】【Python】【三数之和】【双指针】【中等】
文章目录 引言三数之和题目描述示例示例1示例2示例3 提示 解决方案3:【双指针】结束语 三数之和 引言 编写通过所有测试案例的代码并不简单,通常需要深思熟虑和理性分析。虽然这些代码能够通过所有的测试案例,但如果不了解代码背后的思考过程…...

02_Web开发基础之JavaScript
Web开发基础之JavaScript 学习目标和内容 1、能够描述Javascript的作用 2、能够使用分支结构if语句逻辑判断 3、能够使用其中一种循环语句 4、能够定义javaScript中的函数 5、能够定义javaScript中的对象 6、能够描述DOM的作用 7、能够通过DOM操作HTML标签元素及其属性 8、能够…...
如何控制Elasticsearch搜索的相关性?
控制相关性 纯粹处理结构化数据(例如日期、数字和 字符串枚举)很简单:他们只需要检查一个文档(或 行,在关系数据库中)与查询匹配。 虽然布尔值是/否匹配是全文搜索的重要组成部分,但它们 光靠自己是不够的。相反,我们还需要知道每个的相关性 document 是查询。全文搜索…...

基于urllib库的网页数据爬取
实验名称: 基于urllib库的网页数据爬取 实验目的及要求: 【实验目的】 通过本实验了解和掌握urllib库。 【实验要求】 1. 使用urllib库爬取百度搜索页面。 2. 使用urllib库获取百度搜索的关键字搜索结果(关键字任选)。 实验原理及…...

Python如何匹配库的版本
目录 1. 匹配库的版本 2. Python中pip,库,编译环境的问题回答总结 2.1 虚拟环境 2.2 pip,安装库,版本 1. 匹配库的版本 (别的库的版本冲突同理) 在搭建pyansys环境的时候,安装grpcio-tools…...

日志审计在网络安全中的重要性
日志审计是一种通过分析、识别和验证各种日志信息,以帮助企业了解其网络和系统的安全状态和活动的过程。这些日志信息可能来自各种来源,包括服务器、网络设备、应用程序、操作系统等。 日志审计的主要功能包括: 1.识别潜在的安全威胁&#…...
浅谈基于不信任的防御性编程
背景 在实际开发过程中,我们经常遇到这样的场景: 后端报错了,手忙脚乱一顿排查,发现是前端传的参数为空,或者格式不对;后端又报错了,传参没问题,根据日志流发现,是某“给…...

线性代数(一)
1.标量:标量由只有⼀个元素的张量表⽰。 x np.array(3.0) y np.array(2.0) x y, x * y, x / y, x ** y (array(5.), array(6.), array(1.5), array(9.))2.向量:向量可以被视为标量值组成的列表,列向量是向量的默认⽅向。 x np.arange(4…...
k8s-learning-why we need pod
应用场景 应用从虚拟机迁移到容器中 为什么虚拟机中的应用不能无缝迁移到容器中 虚拟机中应用:一组进程,被管理在systemd或者supervisord中 容器的本质:一个容器一个进程 所以将运行在虚拟机中的应用无缝迁移到容器中,与容器…...

【CASS精品教程】cass11提示“请不要在虚拟机中运行此程序”的解决办法
文章目录 一、问题提示二、解决办法一、问题提示 按照正常安装教程安装好南方测绘cass 11之后,打开的时候可能会有以下提示:请不要在虚拟机中运行此程序,如下图所示: 遇到问题,咱们就想办法解决问题,下面将自己尝试的方法及最终解决情况跟大家说一下,供参考。 二、解决…...

【算法Hot100系列】正则表达式匹配
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
html 基础学习笔记
Date:20231212 html标签 基础学习笔记 一、web和internet 1.1、Internet简介 Internet 是一个全球性的计算机互联网络,中文名称有"因特网"、“国际互联网”、“网际网”、"交互网络"等Internet提供的主要服务 Telnet、Email、www、BBS、FTP等…...
7-4 天梯赛的善良
天梯赛是个善良的比赛。善良的命题组希望将题目难度控制在一个范围内,使得每个参赛的学生都有能做出来的题目,并且最厉害的学生也要非常努力才有可能得到高分。 于是命题组首先将编程能力划分成了 106 个等级(太疯狂了,这是假的&…...

案例精选|聚铭综合日志分析系统助力长房集团“智慧房产”信息化建设
长沙房产(集团)有限公司(简称“长房集团”)始创于2004年3月,是一家由长沙市人民政府授权组建的国有独资企业。截至2021年底,企业总资产逾452亿元,总开发面积1300多万平方米,已开发项…...

HarmonyOS给应用添加消息通知
给您的应用添加通知 通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息,帮助用户高效地处理任务。应用可以通过通知接口发送通知消息,用户可以通过通知栏查看通知内容,也可以点击通知来打开应用,通知主要有以下使用场景…...

【C语言】cache和程序访问的局部性对程序性能的影响
文章目录 1.源程序比较其性能影响2.内存分配(1)静态存储区(static):(2)栈区(stack):(3)堆区(heap&…...
数字棱形(课程F)
输入1个整数N,输出N行的如下形状的数字棱形。 例如:N4时: ___1 __222 _33333 4444444 _33333 __222 ___1 (注:上面使用下划线’_’表示空格,以避免看不清造成误解) 输入格式 第一行1个正整数:N࿰…...

如何查看PHP信息
创建一个 PHP 文件,比如 info.php,在其中添加以下代码: <?php phpinfo(); ?>访问这个文件(例如,在浏览器中输入 http://localhost/info.php),它会显示 PHP 的所有配置信息。在这个页面…...
Vue3+ts实现页面跳转及参数传递
## 列表页 <script lang"ts" setup> import { reactive, toRefs } from vue // 1 引入useRouter路由信息方法 import { useRouter } from vue-router // 2 获取实例 const router useRouter()const gotoDetail (index: string) > {router.push({path: …...

日志框架Log4j、JUL、JCL、Slf4j、Logback、Log4j2
1. JAVA日志框架 1.1 为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控,那么如果程序出现异常错误时要如何排查呢?并且系统在运行时做了哪些事情我们又从何得知呢?这个时候日志这个概念就出现了,日志的出现对系…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...