Digital Garden
链表双指针法则:快慢指针与边界防御
🌱 Seedling算法链表双指针LeetCode
为什么全世界程序员都用 slow 和 fast?探讨链表算法中的命名心智模型与防御性编程实践。
在刷链表题目时,很多时候我们不仅是在写代码,更是在与读代码的人(面试官)建立某种“心智共鸣”。
1. 变量命名的“接头暗号”
以前我习惯用 p 和 p2 来遍历链表,虽然逻辑没错,但在复杂的逻辑中容易迷失。
在链表算法里,把指针命名为 slow 和 fast,就像滑动窗口里的 left 和 right 一样,是全世界程序员共通的**“接头暗号”。面试官一看到这两个词,大脑里一秒钟就能构建出“寻找中点”或“判断环”的画面感,这叫作降低认知负载**。
2. 防御性编程:更直观的 Base Case 处理
以前处理链表单节点时,我喜欢用 if (p === p2) 这种巧妙但稍显隐晦的方式来拦截。
但更符合直觉和防御性编程 (Defensive Programming) 的做法是,在函数的最开头,像城门守卫一样,直接拦截掉极端情况(空链表或单节点):
// 黄金起手式:拦截空节点和单节点
if (!head || !head.next) {
return head;
}
这样做的好处是,后面的核心 while 循环里,你再也不用提心吊胆地担心 fast.next.next 会报出 Cannot read property of null 的致命错误了。
3. 标准模板与实战指引
如果你掌握了 slow 和 fast,可以直接秒杀以下两道高频面试题:
- 🎯 [LeetCode 876. 链表的中间结点]:
fast走两步,slow走一步,fast到底时,slow刚好在中点。 - 🎯 [LeetCode 141. 环形链表]:只要链表有环,
fast像是在操场套圈,迟早会和slow相遇 (slow === fast)。
我的通用快慢指针模板:
function findMiddle(head) {
if (!head || !head.next) return head; // Base case
let slow = head;
let fast = head;
// fast 必须保证自己和下一步存在,才能走两步
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
锻造感悟: 算法的优雅不在于用多么隐晦的技巧压缩代码行数,而在于写出像白话文一样流畅、不会在边缘测试用例崩溃的稳健逻辑。