子集树与回溯法:从枚举到剪枝的搜索思维进阶
很多人第一次接触生成集合的所有子集这个问题脑子里冒出来的往往是位运算for mask in range(1 n)一个循环把所有情况列出来简洁又暴力。但如果你只停留在这一层后面遇到子集和为K0-1背包带重复元素的子集去重这些题目时就会卡住——位掩码只负责枚举不负责决策。子集树配合回溯法才是把枚举升级为搜索的关键一步。这篇文章就围绕子集树本身展开把它的形态、递归代码、复杂度、剪枝和迁移到其他题目的思路一次讲透适合刚开始刷回溯算法的人也适合想把组合搜索类问题统一成一套心智模型的开发者。1. 一道题的两条路位掩码很快但它不是万能钥匙1.1 位掩码的子集生成先看一眼位掩码做法。假设集合[1, 2, 3]n3那么从 0 到 7 的每个整数写成二进制分别对应一个子集101表示选第0个和第2个元素也就是{1, 3}。代码很简单def subsets_by_mask(nums): n len(nums) res [] for mask in range(1 n): subset [] for i in range(n): if mask (1 i): subset.append(nums[i]) res.append(subset) return res这个写法的优点一个字快。没有递归没有栈边界情况也少。1 n直接给出子集总数2^n逻辑上几乎不可能写错。但缺点也很明显它只解决枚举全部子集这一个问题。一旦题目变成找子集和为 target 的子集或者0-1 背包最优解掩码就只能老老实实遍历全部2^n种情况没有任何提前退出的余地。你要剪枝不好意思位掩码连当前已经超过目标这种判断都无处安放因为它是一个扁平循环不是一棵树。1.2 为什么回溯要走向树回溯法换了一个视角把决策过程展开成一棵树。每个元素面对一个岔路口向左走表示不选它向右走表示选它。从根节点走到任意一个叶子节点路径上所有选过的元素组成一个子集。n 个元素就是 n 层岔路整棵树有2^n个叶子恰好对应全部子集。这棵树就是子集树它通常是回溯算法教科书里的第一棵递归树。为什么树比扁平枚举更值得重视因为树允许你在DFS的过程中携带状态。走到某个节点时你可以知道当前子集和已经超过 target了于是直接掉头不再往下走。这是位掩码做不到的。树给算法加了一个中途下车的机制这个机制就是剪枝。剪枝不是子集树独有的但子集树是最简单的剪枝训练场——你不需要理解图、不需要理解棋盘只要搞清楚选/不选两个分支就够了。2. 子集树长什么样从根到叶子的每一层都在做选择2.1 一棵完满二叉树以[1,2,3]为例。根节点是空集代表还没对任何元素做决定。第一层遇到元素 1产生两个分支左边不选子集仍为空右边选子集变成[1]。第二层遇到元素 2在第一层的两个节点上各自再分两支于是得到[]、[2]、[1]、[1,2]四个状态。第三层处理元素 3再翻倍最终得到 8 个叶子。把整棵树画出来它是一棵高度为 n 的完满二叉树。这里完满指的是每个内部节点都有两个分支没有任何节点只有单个孩子。这种结构非常规整所以纸面上很美观但它也意味着一个残酷的事实没有剪枝的情况下你没法跳过任何一个节点。每个节点都要访问一次因为每个叶子都代表一个合法的结果。于是访问节点总数是1 2 4 ... 2^n 2^(n1) - 1叶子数2^n内部节点数2^n - 1。n10 的时候叶子数 1024看似不多n20 就是一百多万n30 直接飙到十亿。这就是为什么生成全部子集本身就很难优化——输出规模摆在那里。2.2 深度和状态传递沿着树走的过程中有两样东西在变化当前在树的第几层以及当前已经选入子集的元素列表。在代码实现里层数通常用一个index参数表示已选元素用一个path列表表示。有意思的是子集树每一层的选择只依赖当前这个元素要不要而不依赖前面选了什么。换句话说决策是局部的、独立的。这既是优点也是陷阱优点是代码结构清晰缺点是新手容易误以为所有回溯都这么简单然后跳到排列树时被前面选过的不能选这种全局约束搞晕。2.3 递归本身就是树的遍历如果你写过树的先序遍历那么子集树的递归代码几乎不需要额外学习成本。树的遍历是访问节点 → 递归左子树 → 递归右子树子集树是处理完当前元素的选/不选 → 继续递归下一个元素。本质上就是把一棵树用DFS的方式走完。因此我建议把子集树当成回溯法的Hello World来练习不是因为它简单到不需要练习而是因为它能清晰暴露你对递归的理解程度。你要是能把子集树的递归压栈、返回、回溯这一串动作讲明白后面的排列、组合、棋盘搜索都只是锦上添花。3. 手写一个能用的回溯模板代码、轨迹和经典坑3.1 Python实现选/不选写法直接给一个最标准的子集树回溯实现def subsets(nums): res [] path [] def dfs(index): if index len(nums): res.append(path[:]) return # 分支一不选 nums[index] dfs(index 1) # 分支二选 nums[index] path.append(nums[index]) dfs(index 1) path.pop() dfs(0) return res以nums [1, 2]跑一遍输出顺序是[] [2] [1] [1, 2]注意这个顺序先是一直往左走到叶子得到空集然后回溯一层走右分支得到[2]再往上回溯走根节点的右分支……这就是DFS先深后广的典型顺序。代码里最关键的一行是最后的path.pop()。path是递归全程共享的一个列表你在右分支里往它尾部添加了一个元素如果不加回退左分支再往下走时就会带着这个残留元素导致结果完全错乱。append和pop必须成对出现这是回溯的婚姻法则。3.2 另一个常见写法组合式DFS刷题时更常见的其实是下面这种写法它不严格按每个元素一分为二组织而是按下一个元素选谁来递归def subsets_comb_style(nums): res [] path [] def dfs(start): res.append(path[:]) for i in range(start, len(nums)): path.append(nums[i]) dfs(i 1) path.pop() dfs(0) return res每次进入dfs(start)时先把当前path保存下来这相当于记录从根到当前节点的这条路径然后用一个 for 循环枚举下一个可以加入子集的元素位置从start开始避免回头选到前面的元素造成{1,2}和{2,1}重复。这两种写法的本质是一样的都在遍历子集树。区别在于第一种用两个显式分支表示选/不选第二种用start参数控制下一次从哪里开始选每一层实际有多个分支。第二种写得更紧凑而且在后面做固定长度组合和重复元素去重时推广起来非常自然所以面试中更常见。3.3 三个经典错误第一忘记path[:]拷贝。如果直接res.append(path)最终res里的所有元素都会变成同一个列表的引用因为path在递归结束后会被清空。你得到的结果将是[[], [], [], []]这样的地狱。第二append和pop不对称。有些人只在某个分支里pop另一个分支不处理导致元素残留。第三递归出口条件写错。index len(nums)表示所有元素都已经决策完毕这时候才能记录叶子如果把出口写在index len(nums) - 1你就会漏掉最后一个元素的决策少生成一半子集。3.4 其他语言与迭代版本C版本主要是把path以引用传递避免反复拷贝class Solution { public: vectorvectorint subsets(vectorint nums) { vectorvectorint res; vectorint path; dfs(0, nums, path, res); return res; } void dfs(int idx, vectorint nums, vectorint path, vectorvectorint res) { if (idx nums.size()) { res.push_back(path); return; } dfs(idx 1, nums, path, res); path.push_back(nums[idx]); dfs(idx 1, nums, path, res); path.pop_back(); } };如果你连递归都不想写还有一个迭代版本思路是每来一个元素就与现有所有子集组合形成新子集def subsets_iterative(nums): res [[]] for num in nums: res [sub [num] for sub in res] return res这个写法的源头是动态规划思想它不依赖递归栈代码也更短但它同样没有剪枝能力只能用于生成全部子集这种纯枚举场景。我一般把它当作参考答案真正到面试讲解时还是会先讲回溯因为面试官想听的是状态树的组织方式。4. 复杂度要算两次遍历是O(2^n)输出是O(n·2^n)4.1 调用次数的精确计算很多人背复杂度结论时只背一句生成子集是 O(2^n)这个结论在只遍历不输出的场景下是对的。但当你真正构造结果集时事情没那么简单。先算递归调用次数。子集树一共2^(n1) - 1个节点每个节点上的递归函数执行常数次操作比较、压栈、返回所以单纯的深度优先遍历是O(2^n)。这个阶段和位掩码的1 n循环属于同一量级没有谁优谁劣。再算输出规模。每个叶子代表一个子集把path拷贝进res需要O(k)的时间k 是子集长度。全部叶子的长度总和是多少答案是n * 2^(n-1)。理由很直观每个元素会出现在恰好一半的子集里总共2^n个子集所以每个元素出现2^(n-1)次n 个元素合计就是n * 2^(n-1)。于是整体时间复杂度是O(2^n)遍历O(n * 2^(n-1))输出O(n * 2^n)4.2 为什么这个下界绕不开这里有个值得反复强调的点只要题目要求返回所有子集那么任何算法的时间复杂度至少是O(n * 2^n)因为单是答案本身就有这么多元素和这么多数据要写。你用位掩码也好用回溯也好用迭代法也好这个下限都逃不掉。所以刷这类题时别幻想有低于指数级的解法出现不存在的。这也顺带解释了一个面试中常被问到的点能不能优化 如果面试题只是要求生成全部子集你能回答的优化方向其实是常数层面的比如用path.copy()替代切片、用数组预分配结果空间、在 C 里用res.reserve(1 n)提前扩容。这些手段降低的是大O后面的常数不是指数本身。4.3 空间复杂度与工程瓶颈递归栈的深度是 n所以额外空间是O(n)。但结果集本身占据了O(n * 2^n)的空间这才是真正的内存杀手。实际工程里n20 时输出元素个数是20 * 2^19 ≈ 1048万个整数Python 里一个整数加一个指针开销轻松超过 300MB已经不适合在容器环境里直接运行。n25 就基本是天文数字了。所以我在实际项目里如果需要真的枚举子集一般会先问自己一句能不能改成边生成边处理也就是把消费结果的逻辑直接写在递归叶子处而不是把整个结果集堆在内存里再事后处理。这样内存占用从O(n * 2^n)降到O(n)很多搜索类题目其实都能这么干。4.4 什么时候复杂度会失控有一种常见误区认为回溯自带剪枝就一定能跑大输入。记住剪枝的前提是存在不需要访问的节点。生成全部子集恰恰没有这种节点每一个叶子都是答案所以回溯在这里并不比位掩码快。真正体现回溯优势的题目是找满足条件的组合或判断是否存在解这时剪枝能砍掉大量分支实际运行时间会远小于最坏情况的O(2^n)。5. 剪枝才是子集树的灵魂去重、子集和与0-1背包5.1 有重复元素的子集去重LeetCode 风格的题目里输入往往是[1, 2, 2]要求输出不重复的子集。如果不做任何处理[2]会出现两次一次由第一个 2 产生一次由第二个 2 产生。去重的标准套路是先排序再在某层的 for 循环里跳过与上一个相同且上一个没有被选择的元素。用组合式DFS写def subsets_with_dup(nums): nums.sort() res [] path [] def dfs(start): res.append(path[:]) for i in range(start, len(nums)): if i start and nums[i] nums[i - 1]: continue path.append(nums[i]) dfs(i 1) path.pop() dfs(0) return res这个i start条件就是同层去重在同一个dfs(start)的 for 循环里如果两个相邻元素值相同就跳过后续那个。为什么i start而不是i 0因为i start时当前元素是这一层的第一个选择它没有同层前驱即使nums[start] nums[start - 1]也代表的是上一层不同分支选择的元素不能算重复。这个细节搞错的人非常多建议手动跑一遍[1, 2, 2]验证。如果坚持用子集树的选/不选二分支模板去重逻辑可以写成选当前元素时如果它等于前一个元素且前一个元素在这一层没有被选过则跳过。两种写法等价但组合式更不容易漏条件我推荐先用熟组合式。5.2 子集和问题的剪枝真正体现子集树价值的经典题是子集和问题给定数组和 target判断是否存在某个子集的和等于 target。朴素思路是枚举全部子集逐个求和。但这样做在 n 较大、target 较小的时候会浪费大量时间。回溯剪枝版本def possible_subset_sum(nums, target): nums.sort() n len(nums) def dfs(idx, current_sum): if current_sum target: return True if current_sum target or idx n: return False # 不选当前元素 if dfs(idx 1, current_sum): return True # 选当前元素 return dfs(idx 1, current_sum nums[idx]) return dfs(0, 0)这里的剪枝条件是current_sum target。一旦当前子集和超过目标整个子树都不可能有合法结果直接返回 False后续元素全都不用再试。这个简单判断可以大幅减少搜索空间。比如nums [1, 2, 3, ... 30]target 10很多分支会在前几层就超重退出。如果你只关心存在性而不是所有方案记得每次递归返回一个布尔值并且一旦找到解就一路向上返回 True避免继续搜索其他分支。很多人在这个位置写错找到解后不知道如何终止整棵树的后续遍历结果搜索完成才返回剪枝效果直接打折扣。5.3 0-1背包的贪心剪枝思路0-1 背包问题里每件物品同样只有选/不选两个状态子集树天然覆盖整个解空间。搜索每个叶子算总价值和总重量取最优即可。但实际比赛或面试里 n 稍大就扛不住于是要用更强的剪枝。一个极其实用的剪枝是剩余物品价值上限估算在当前节点如果当前价值 剩余所有能放的物品价值总和 已找到的最佳价值则剪掉这个子树。这个估算可以进一步用按单位重量价值排序后用分数背包的松弛解作为上界来实现——这就是经典的分支限界思路。这类剪枝的价值不在于理论复杂度变低而在于实际搜索空间大幅缩水。同一个 n40 的数据无剪枝回溯可能跑到天荒地老加了上界剪枝后可能几毫秒就结束了。所以说子集树不只是一个简单的集合枚举工具它更是理解状态空间 约束 剪枝三位一体思想的最佳起点。6. 从子集树出发组合、排列与搜索题的心智模型6.1 固定大小子集剪掉不够长的分支子集问题稍加改动就变成组合问题从 n 个元素中取 k 个返回所有组合。组合本质上就是长度为 k 的子集所以仍然可以用子集树框架只是要把叶子改成长度等于 k 的节点。组合式DFS写法def combine(nums, k): res [] path [] def dfs(start): if len(path) k: res.append(path[:]) return for i in range(start, len(nums)): path.append(nums[i]) dfs(i 1) path.pop() dfs(0) return res还可以加一个经典剪枝如果len(path) (len(nums) - start) k说明就算把后面所有元素全选上也不够长度直接 return。这个判断对性能提升很大尤其在 n 大、k 接近 n 或接近 0 的场景下。6.2 排列树和子集树的对照很多人学完子集就去刷排列结果发现代码长得挺像但总在used数组上出错。排列问题的递归树不是子集树而是排列树每一层尝试剩下还没用过的元素所以每一层的分支数递减。两者的核心差异可以用一句话概括子集树关心每个元素要不要状态是index排列树关心下一个放哪个还没放过的元素状态是used。理解了这一点你就能明白为什么排列的递归函数里必须有一个used[i]数组来防止同一个元素被重复使用而子集树里只需要index 1就能天然避免回头。子集和排列的代码看起来很像但它们的树结构完全不同调 bug 的时候一定要先画树再对着代码找错。6.3 我写回溯的固定习惯最后分享几个我个人写回溯题时一直沿用的操作习惯这些东西在文档里未必找得到但能显著减少调试时间。第一先画递归树再写代码。就算只是子集生成这种简单题我也会在草稿纸上画出 n3 的完整树结构标出每个节点的index和path。很多边界条件比如为什么这个剪枝条件写在递归前而不是递归后画完树就一目了然了。第二在递归函数入口打印状态调试。比如def dfs(index, path): print( * index findex{index}, path{path})一旦递归层数多了打印缩进能像树形图一样把整个搜索过程摆在你面前。找path.pop()漏写的 bug这种方法比看代码快太多。第三永远先想清楚叶子在哪里。子集问题的叶子是index n组合问题的叶子是len(path) k子集和问题的叶子是current_sum target。把叶子定义清楚递归出口就不会写错也不会在非叶子节点误存结果。几乎所有回溯 bug 都出在在错误的层记录了结果或忘记在返回后恢复状态这两类上。子集树看起来只是回溯算法里最基础的一课实际上它承载的思想——用树组织决策、在DFS中维护状态、按需剪枝——几乎贯穿所有搜索类问题。把这一课吃透后面再遇到排列、组合总和、N皇后、数独这类题目时你就不会觉得它们是散装的题而是同一棵树上开出的不同分支。我的建议是不用急着背各种题的模板先把子集树自己在纸上完整画一遍然后用你会的一门语言实现它再试着给它加一两个剪枝条件。这几步走完回溯的门就算真正迈进去了。

相关新闻

CATIA许可证管理端不弹窗?从进程、服务与显示设置排查修复

CATIA许可证管理端不弹窗?从进程、服务与显示设置排查修复

简介:CATIA在WIN10系统下运行时常遇到DS License Server Administration无法弹出许可证管理窗口的故障,影响正常建模与授权使用。它针对这一问题提供系统化的排查与解决思路,面向CATIA使用者、安装运维人员及需要重新配置授权的工程师。资源为…

2026/10/14 6:42:30 阅读更多 →
计算机视觉期末复习:从卷积到SIFT的习题攻克指南

计算机视觉期末复习:从卷积到SIFT的习题攻克指南

简介:这是一份面向计算机视觉课程期末复习的习题整理资料,基于章毓晋《计算机视觉教程》(微课版第3版)章节内容编排,覆盖距离计算、图像采集设备、相机成像、梯度与拉普拉斯算子、哈夫变换、形状分析、双目立体视觉、运…

2026/10/14 6:42:30 阅读更多 →
Java基础探秘:JSON解析与反射机制如何协同工作

Java基础探秘:JSON解析与反射机制如何协同工作

最近在写一个接口的时候,我盯着IDE里那几十行setter调用,脑子里突然冒出一个问题:前端传过来一段JSON字符串,我一行代码就把它变成了业务对象,中间到底是谁在帮我完成键名和字段的匹配?顺着这个疑问查下去&…

2026/10/14 6:41:29 阅读更多 →

最新新闻

AI编程教程为何爆火2万Star:从零基础到生产落地的完整路径

AI编程教程为何爆火2万Star:从零基础到生产落地的完整路径

1. 这个“2万Star”到底意味着什么:从GitHub数字看AI编程教程的真实分量很多人看到标题里“2万Star”第一反应是:哇,火了。但作为在开发者教育领域摸爬滚打十多年的老手,我得先泼一盆冷静的水——Star数从来不是教学内容质量的直接…

2026/10/14 7:19:37 阅读更多 →
DNN多分类任务核心设计:Softmax、交叉熵与PyTorch实战

DNN多分类任务核心设计:Softmax、交叉熵与PyTorch实战

2. 多分类任务的三个核心设计问题先说结论:DNN跑多分类,真正需要想清楚的只有三件事——输出层的神经元数量、输出层的激活函数、损失函数的选择。这三件事定下来,模型框架基本就定死了,后面做的都是调参和优化。2.1 输出层神经元…

2026/10/14 7:19:37 阅读更多 →
离线环境部署MySQL 5.7:tar.gz包安装与避坑指南

离线环境部署MySQL 5.7:tar.gz包安装与避坑指南

简介:本资源为 MySQL 5.7.40 在 Linux-glibc2.12 环境下的 x86_64 离线安装包,面向需要在无外网或内网环境中部署关系型数据库的运维与后端开发人员。压缩包共 379 个文件,约 646.59MB,以 h 头文件、so 动态库、xml 配置、sql 脚本…

2026/10/14 7:19:37 阅读更多 →
华为HCIP-AI测试卷高效复习指南:从背答案到动手验证

华为HCIP-AI测试卷高效复习指南:从背答案到动手验证

简介:这份华为认证HCIP-AI测试卷与答案文档,面向备考华为人工智能高级认证的考生及希望系统检验AI基础知识的开发者,帮助梳理图像处理、语音合成、HiAI生态与快应用开发等核心考点。资源包共1个docx文件,约14KB,内容以…

2026/10/14 7:19:37 阅读更多 →
基于rSVD与软阈值的Matlab高效信号去噪方法

基于rSVD与软阈值的Matlab高效信号去噪方法

1. 项目背景与核心挑战谐波噪声是信号处理领域常见的干扰类型,广泛存在于电力系统监测、机械振动分析、生物医学信号采集等场景。传统去噪方法如傅里叶滤波、小波变换在面对高维大数据集时往往面临计算效率低下、噪声鲁棒性不足的问题。这个Matlab项目提出了一种融合…

2026/10/14 7:19:37 阅读更多 →
智慧农业最后一百米:破解乡村数字化落地的设备与运维难题

智慧农业最后一百米:破解乡村数字化落地的设备与运维难题

做了快八年农业数字化项目,我最深的一个体会是:智慧农业真正难的从来不是算法有多高级、传感器有多精密,而是怎么让技术在没有网线、没有专职IT人员、甚至不少农户只用老年机的村子里,持续稳定地转起来。前段时间复盘了一个扎根基…

2026/10/14 7:18:36 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →