双指针算法详解:从对撞、快慢到滑动窗口的实战应用
双指针这个算法我在面试和实际项目里都吃过不少亏也尝过甜头。很多人一听到“双指针”觉得简单无非就是两个下标来回动可真到做题、写业务代码的时候要么想不到用它要么写出了死循环要么边界差一个数。这篇文章我就把双指针这件事彻底摊开讲清楚它解决的是什么问题、三种常见形态各自怎么用、实战里那些一踩一个准的坑以及我自己的排查经验。先给新朋友一个定位双指针大多用于处理数组、字符串、链表这类线性结构核心作用是把“两层循环暴力枚举”的 O(n²) 复杂度压缩到 O(n) 或 O(n log n)。它本身不依赖复杂数据结构就是通过两个“指针”的移动策略跳过那些明显不可能产生答案的组合。适合正在准备笔试面试的开发者也适合工作中要处理数据匹配、去重、区间问题时想优化性能的人。1. 双指针到底解决什么问题1.1 暴力解法的瓶颈在哪里绝大多数双指针题目的暴力版本都是同一个套路两层 for 循环枚举所有组合。比如“在一个有序数组里找两个数使它们的和等于目标值”暴力写法很自然def two_sum_brute(nums, target): n len(nums) for i in range(n): for j in range(i 1, n): if nums[i] nums[j] target: return [i, j] return []这代码能跑但数据量一上来就崩。数组长度是 10^5 时两层循环就是 10^10 次操作基本超时。关键问题在于它把每一对组合都遍历了一遍其中大量组合根本不需要检查。暴力解为什么慢因为它默认“任何两个元素都有可能组成答案”于是必须全量枚举。可很多场景里数据本身带有顺序信息比如数组有序、或者问题存在单调性这种信息暴力解法完全没有利用。1.2 双指针如何消除无效组合双指针的核心逻辑是让指针按照某种规则移动每移动一次就排除掉一批“不可能成为答案”的组合。还拿有序数组两数之和举例。数组是[1, 3, 5, 7, 9, 11]目标值是 12。暴力做法会先看 1 和 3、1 和 5、1 和 7…… 但如果我让一个指针left指向开头一个right指向末尾先看1 11 12刚好命中。假设目标换成 101 11 12 10说明什么right指向的 11 跟“当前最小的 left 指向的 1”相加都超过目标那 11 跟数组中任何其他元素相加必然更大。于是right这个位置可以直接排除右指针左移。同理如果两数之和小于目标说明left指向的元素跟“当前最大的 right 指向的元素”相加还不够那它跟任何更小的元素相加也不够left右移。这个“排除一批组合”的思想就是双指针的魂。每次移动指针不是随机乱走而是根据已有的数据规律合理地剪掉一整片搜索空间。所以双指针的时间复杂度是 O(n)左右指针合计最多移动 n 次每次判断是 O(1)。理解了这个后面所有双指针题目都好办了。你不需要背模板只需要问自己指针移动时能不能确定某些组合已经无效了2. 双指针的三种常见形态双指针不是一种固定写法按移动方向和使用场景我习惯把它分成三类对撞指针、快慢指针、滑动窗口。三类搞明白基本能覆盖绝大多数题目。2.1 对撞指针一个从左一个从右对撞指针也叫左右指针两个指针分别从数组两端出发向中间移动直到相遇。最典型的应用场景是有序数组、回文判断、反转数组。适用前提通常有两个数据本身有某种单调性最典型的是有序问题的解跟“两端元素组合”有关。对撞指针的通用框架长这样left, right 0, len(nums) - 1 while left right: if 满足条件: # 记录/返回 left 1 right - 1 # 具体看题目 elif 需要左移: left 1 else: right - 1边界条件要特别注意。循环到底该用left right还是left right取决于指针相遇时这个元素还需不需要参与判断。比如反转数组[1, 2, 3, 4, 5]交换 1 和 5、交换 2 和 4中间那个 3 不需要跟谁交换所以用left right。如果是“判断数组里是否存在两个数之和等于目标”当left right时指向同一个元素同一个元素不能用两次所以也是left right。这些细节想清楚一次后面就不会再错。对撞指针我最常用来讲的一个例子是盛水容器题。给一组高度找两条线跟 x 轴组成容器能盛最多水。暴力是枚举所有 i、j 组合对撞指针则从最宽处开始每次移动高度较短的那一侧。因为容器高度取决于短板移动长板水量只会更少移动短板才有机会变大。这个“移动短板”的判断就是对撞指针最经典的“排除无效状态”的场景。2.2 快慢指针一个跑得快一个跑得慢快慢指针两个指针从同一端出发一个每次走两步一个每次走一步常用于链表。最经典的应用是判断链表是否有环如果有环快指针终究会“追上”慢指针如果无环快指针会先走到链表末尾。为什么能追上这其实是个简单的相对速度问题。快指针每次比慢指针多走一步在环里就相当于每轮追近一步有限轮内必然相遇。如果没环快指针会先碰到None循环自然终止。判断链表是否成环的代码很简单def has_cycle(head): slow fast head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: return True return False这里有两个常见的坑初始化时slow和fast都要指向head不能一个指向head、一个指向head.next虽然也能判断环但相遇点会变化后面如果要找环入口就会乱套循环条件必须是fast和fast.next都不为空。因为快指针一次走两步如果fast.next已经为空再取fast.next.next就直接报错。快慢指针的另一个高频应用是找链表中间节点。快指针到末尾时慢指针正好在中间。这个技巧在链表排序里很关键比如归并排序找中点就是用快慢指针实现的。有序数组原地去重也可以看成同向双指针的变种一个指针指向“已处理区间的末尾”一个指针负责遍历。写法上很像快慢指针但两者速度都是“一步”区别在于慢指针只在特定条件下前进。2.3 滑动窗口双指针里的“区间维护者”滑动窗口本质也是双指针只不过两个指针维护的是一个连续区间而不是两个独立位置。left指向窗口左边界right指向窗口右边界right不断右移扩大窗口当窗口不满足某个约束时left右移缩小窗口。典型场景是子串、子数组问题最长无重复子串、最小覆盖子串、长度最小的子数组等等。滑动窗口的框架我用一个模板就能说清left 0 for right in range(len(s)): # 更新窗口内状态 # 当窗口不满足条件时收缩左边界 while 窗口不合法: # 更新状态去掉 s[left] left 1 # 此时窗口合法记录答案这里必须强调right负责“扩张”left负责“收缩”扩张和收缩的时机是滑动窗口的精髓。很多人写滑动窗口写不明白就是因为把两个指针的职责搞混了一会儿想右移一会儿想左移最后状态更新乱七八糟。滑动窗口能优化的原因也值得多说一句。普通暴力枚举所有子区间是 O(n²)但滑动窗口里每个元素至多被right进一次、被left出一次总操作次数是 O(2n)也就是 O(n)。对于字符串类问题这个优化幅度是质变的。三种形态不是互斥的很多题可以多个角度理解。比如判断回文串既是对撞指针也可以理解成“从两端往中间缩小的窗口”。脑袋里能把三个形态串起来看到题目就能自动归类。3. 实战拆解几道绕不开的经典题这里我挑几道覆盖面广的题每一道都写一下思考过程、解法、代码和易错点。这些题吃透了双指针基本就过关了。3.1 有序数组两数之和题目描述给定一个升序排列的整数数组和一个目标值找出两个数使它们的和等于目标值返回这两个数的下标。思路对撞指针。left指向最小值right指向最大值。当前和偏大说明right太大右指针左移当前和偏小说明left太小左指针右移。def two_sum(nums, target): left, right 0, len(nums) - 1 while left right: cur nums[left] nums[right] if cur target: return [left, right] elif cur target: left 1 else: right - 1 return [-1, -1]为什么必须有序对撞指针依赖“当前和偏大则右移”这个判断。如果数组无序nums[right]不一定是当前区间最大值这个排除逻辑就不成立。无序数组要两数之和那得用哈希表这是另外一类解法。扩展如果题目要求返回所有不重复的组合呢那就不能找到一个就返回而是找到一个后继续移动指针同时跳过重复元素。这个逻辑在“三数之和”里更有存在感。3.2 盛最多水的容器题目描述给定一组高度heightheight[i]表示位置 i 处隔板的高度选两个位置构成容器求最大容量。思路对撞指针。容量等于min(height[left], height[right]) * (right - left)。数组是“宽度”有序的最宽的组合是两端之后宽度一定减小。我们只需保证在每次宽度缩小时放弃的是那个“不可能产生更大容量”的边界。关键判断如果height[left] height[right]移动right会怎样新的容量高度还是height[left]因为短板没变但宽度变小容量必然变小。所以移动right没有意义应该移动left因为left变成更高的板子时容量才有可能变大。def max_area(height): left, right 0, len(height) - 1 ans 0 while left right: area min(height[left], height[right]) * (right - left) ans max(ans, area) if height[left] height[right]: left 1 else: right - 1 return ans易错点很多人会纠结“如果两板高度相等该移哪边”答案是无所谓任意移动一边都行因为高度相等的两边移动任意一边都不会让短板变高但宽度减少容量不会变大两边都是“无效移动”。这个题的价值在于对撞指针不一定要求数组“有序”只要有“单调的排除规律”就行。3.3 三数之和题目描述给定一个整数数组找出所有不重复的三元组使得三元组之和为 0。思路排序 对撞指针。先固定一个数nums[i]剩下两个数就是“有序数组两数之和等于 -nums[i]”的问题。为了去重需要跳过重复的nums[i]找到一组答案之后对left和right也跳过重复。def three_sum(nums): nums.sort() res [] n len(nums) for i in range(n - 2): if i 0 and nums[i] nums[i - 1]: continue left, right i 1, n - 1 target -nums[i] while left right: cur nums[left] nums[right] if cur target: res.append([nums[i], nums[left], nums[right]]) while 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 elif cur target: left 1 else: right - 1 return res这个题最大的坑是去重。我第一次写的时候只对第一层循环做了去重结果[-1, 0, 1]和[-1, 1, 0]这种组合会重复出现。后来才意识到找到一个答案后必须把left和right附近的重复元素全部跳过否则同样的三元组换个顺序又会被记录一次。还有个小优化外层循环里如果nums[i] 0因为数组已经升序后面的数更大三数之和不可能为 0直接break。这个剪枝能让代码在全是正数的数据上跑得飞快。3.4 快慢指针判断链表是否有环这道题我在前面已经写过代码了这里再补充两个延伸点。第一个延伸是找环的入口。判断有环还不够很多题要你返回环开始的节点。方法是一个经典结论快慢指针相遇后把一个指针重新指向head两个指针每次都走一步再次相遇的位置就是环的入口。为什么设链表头到环入口距离为 a环入口到相遇点距离为 b相遇点继续走到环入口距离为 c。快指针走的路程是慢指针的两倍有a b n(bc) b 2(a b)化简得a n(bc) - b (n-1)(bc) c。也就是说从头出发走 a 步和从相遇点走 c 步再加上走若干整圈在环入口汇合。这个推导建议自己画个图推一遍光背结论容易忘。第二个延伸是链表中间节点。快指针每次走两步慢指针每次走一步快指针到末尾时慢指针正好在中间。这道题就是“偶数个节点时返回中间靠前还是靠后”的边界问题。一般“找中间节点”都要求如果链表有偶数个节点返回第二个中间节点那慢指针位置刚好满足不需要额外处理。3.5 滑动窗口最长无重复子串题目描述给定一个字符串找出其中不含重复字符的最长子串的长度。思路窗口内维护一个“字符 → 出现次数”的映射。right每次进入一个新字符把它加入窗口如果这个字符出现次数大于 1说明窗口内有重复left不断右移直到窗口重新合法。每次合法时记录窗口长度。def length_of_longest_substring(s): from collections import defaultdict window defaultdict(int) left 0 ans 0 for right in range(len(s)): c s[right] window[c] 1 while window[c] 1: d s[left] window[d] - 1 left 1 ans max(ans, right - left 1) return ans这里while window[c] 1是收缩条件因为只有新加入的字符可能导致重复所以判断条件只盯着c就行不用检查整个窗口。如果追求极致可以用一个数组记录每个字符最后一次出现的位置左指针直接跳到重复字符上一次出现位置的后面省掉while逐步收缩的过程。写法更高效但对初学者理解不够直观。我建议先掌握哈希表 while收缩的版本把滑动窗口逻辑理顺再去做优化。4. 常见问题与排查技巧实录双指针代码看着短但错起来很隐蔽。我把这几年自己踩过、也看别人踩过的坑整理成一个速查表每一条都值得记下来。问题现象常见原因处理方式代码死循环指针不前进更新指针的语句被写在条件分支之外或者分支条件写反每个分支都要保证至少有一个指针移动返回结果多算一个数left right写成了left right相遇元素被重复使用根据题意确认相遇元素是否需要参与计算结果漏算一个数条件写成了left 1 right把中间紧邻的两个元素漏掉了用具体小数组走读一遍边界去重后仍然有重复答案只去重了外层固定元素没去重内层指针附近的重复值找到答案后跳过 left 和 right 两侧重复值快慢指针判断环时报空指针循环条件只判断了fast没判断fast.next必须写成while fast and fast.next滑动窗口状态没更新干净左边界右移时忘了把对应元素从窗口计数中减掉收缩窗口的每一步都要同步更新状态对撞指针贪快写反了移动方向没有明确“哪边移动能排除无效组合”先论证移动某一边后哪些组合被排除我排查双指针问题的方法很土但很有效拿一个长度 5 左右的小数组手动模拟指针的每一步打日志输出left、right和当前状态。绝大多数边界错误走一遍就暴露了。还有个经验写循环条件之前先问自己“指针相遇后还需不需要处理”需要则不需要则。这个问题想清楚了边界就不会错。另外如果你在面试里写双指针建议先跟面试官沟通思路再动手重点说明“为什么指针移动能排除无效组合”。这比闷头写完代码更加分因为面试官真正想考察的往往是你能不能讲清楚“为什么 O(n) 是对的”。5. 这些延伸场景也别忽略双指针的应用面比想象中广我再补充几个容易忽略的场景。归并两个有序数组其实就是双指针思想两个指针分别指向两个数组的头部每次比较大小把较小的放入结果数组然后对应指针后移。这在归并排序里是核心步骤也是双指针的入门应用之一。注意这里两个指针指向的是不同数组属于“双序列双指针”不像前面多是单序列上的操作。链表拆分比如把链表按奇偶位置拆成两个链表或者按某个值分成小于和大于两部分也可以用一个遍历指针加一个插入指针来实现。这类题不难但能把双指针从“数组场景”迁移到“链表场景”算是能力进阶。判断回文链表可以先快慢指针找到中点然后反转后半部分链表最后两个指针从两端向中间逐个比较。这道题把快慢指针、链表反转、双指针比较串起来了综合性强很值得一练。我每次带人刷链表题都会把这道题放在最后因为前面三道基础题的知识点全都覆盖到了。两个排序数组找交集也是双指针。两个指针分别从两个数组开头走元素相等则记录小的那个指针往后走。这个思路跟我之前讲的两数之和很像但场景变成了双序列理解起来反而更简单。这些延伸场景有一个共同特点都是把“暴力枚举所有可能性”变成“利用顺序信息跳过无效情况”。所以你会发现双指针其实不是一种“技巧”而是一种“思维方式”。用一句话总结我的体会双指针的难点从来不在代码而在于你能不能敏锐地发现数据里的“单调性”或“顺序关系”。一旦发现了指针怎么移动就水到渠成。如果让我给学习建议我会说先亲手实现一遍有序数组两数之和然后把条件改一改分别用对撞、快慢、滑动窗口三个角度去套课后的题目。不用贪多每一道题做到能白板讲清楚“为什么移动这个指针是对的”为止。刷题数量不说明什么能把“排除无效组合”这句话讲明白才是真掌握。

相关新闻

ffmpeg实战:B站m4s缓存视频合并成MP4指南

ffmpeg实战:B站m4s缓存视频合并成MP4指南

简介:面向需要将B站缓存视频中的audio.m4s与video.m4s合并为mp4的用户,此压缩包提供了一套完整的小型自动化方案。资源仅5个文件,含3个bat批处理脚本和2个Python脚本,压缩包体积仅2KB,非常轻量。Python脚本承担核心逻辑…

2026/10/19 3:16:05 阅读更多 →
PyTorch实现36种果蔬识别:从深度卷积网络到视频流推理的工程实践

PyTorch实现36种果蔬识别:从深度卷积网络到视频流推理的工程实践

做“36种水果和蔬菜识别”这个项目的时候,我其实并没有把它当成一个简单的分类任务来处理。PyTorch框架、深度卷积网络模型、照片连续识别和视频识别,这几个关键词拼在一起,意味着这个项目要同时兼顾精度、速度、部署灵活性和工程化落地。它不…

2026/10/19 3:16:05 阅读更多 →
2019年CSP-S初赛选择题1-5题精讲:进制、位运算、栈、二叉树与KMP陷阱全解析

2019年CSP-S初赛选择题1-5题精讲:进制、位运算、栈、二叉树与KMP陷阱全解析

2019年的CSP-S初赛,是我第一次以“CSP”而不是“NOIP”的名义走进考场。说实话,当时心里多少有点没底——毕竟是新名字的第一年,谁也不知道命题风格会不会大改。等我翻到选择题第一页,看到前5题的时候反而松了口气:进制…

2026/10/19 3:15:05 阅读更多 →

最新新闻

LSTM原理详解与工程加速:从梯度消失到推理优化

LSTM原理详解与工程加速:从梯度消失到推理优化

最近在某个工业设备的时序预测项目里,我把LSTM重新捡起来用了一遍。说实话,这两年Transformer在序列任务上的声量确实大,很多新入行的同学一上来就研究注意力机制,反而把LSTM这个经典的循环结构给忽略了。但手头这个项目跑下来&am…

2026/10/19 4:08:35 阅读更多 →
Linux权限管理实战:chmod、chown与chgrp用法详解

Linux权限管理实战:chmod、chown与chgrp用法详解

在Linux下折腾权限,估计是每个接触它的人逃不掉的一课。我见过不少同事,代码在本地跑得好好的,一部署到服务器就报Permission denied,第一反应就是随便敲个sudo chmod -R 777图省事,结果要么服务能跑了但文件权限全乱&…

2026/10/19 4:08:35 阅读更多 →
Qt虚拟键盘实现:基于QWidget的嵌入式触摸屏输入方案与避坑指南

Qt虚拟键盘实现:基于QWidget的嵌入式触摸屏输入方案与避坑指南

简介:一款基于Qt开发的虚拟小键盘实现,面向需要无物理键盘输入或希望为界面内嵌软键盘的Qt开发者。它采用无边框嵌入方式,不创建独立窗口,而是接收自定义myspinbox(继承自QSpinBox)指针,将键盘的…

2026/10/19 4:08:35 阅读更多 →
AI编程最佳实践:用提示词与代码审查打造高质量代码

AI编程最佳实践:用提示词与代码审查打造高质量代码

最近这段时间,AI 编程大概是开发者圈子里最绕不开的话题。打开技术社区,到处能看到“用 AI 半天写完一个功能”或者“AI 帮我重构了祖传代码”的分享。不过我也发现一个很有意思的现象:同样是开着 AI 编程助手写代码,有人拿到的是…

2026/10/19 4:08:35 阅读更多 →
JavaScript数组按属性分组:从reduce手写到Map安全实现与性能避坑

JavaScript数组按属性分组:从reduce手写到Map安全实现与性能避坑

做开发这几年,“JSON 数组按某个属性分组”绝对是我见过最常被问到的需求之一。后端丢过来一百条订单,前端要按日期分成几块展示;导出报表的时候,要把同部门的数据合并到一起;甚至只是写个数据清洗脚本,也绕…

2026/10/19 4:08:35 阅读更多 →
CefSharp Cookie持久化实战:存储、读取与多账号隔离

CefSharp Cookie持久化实战:存储、读取与多账号隔离

接手过几个需要内嵌浏览器的桌面项目之后,我对“CefSharp存储Cookie和读Cookie”这件事算是从踩坑踩到了门清。很多刚接触CefSharp的朋友会以为,Cookie存取无非就是调一个API的事,但实际上这套基于Chromium内核的封装,在会话持久化…

2026/10/19 4:07:34 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →