链表:单链 / 双链 / 环形
本节目标
- 理解链表节点结构与指针操作
- 掌握反转、合并、环检测、找中间点四大经典题
- 会用"虚拟头节点"简化边界处理
节点与基本操作:
// 运行环境:Node.js 14+
// 保存为 dsa-l5.js,执行:node dsa-l5.js
class ListNode {
constructor(val, next = null) { this.val = val; this.next = next }
}
// 遍历
function traverse(head) {
let cur = head
while (cur) { console.log(cur.val); cur = cur.next }
}
// 插入:先接后断(顺序不能反!)
function insertAfter(prev, node) {
node.next = prev.next
prev.next = node
}
// 删除:跳过目标
function deleteNext(prev) { if (prev.next) prev.next = prev.next.next }虚拟头节点(dummy):统一"删除头节点"和"在头部插入"的边界处理。
function removeValue(head, val) {
const dummy = new ListNode(0, head) // 虚拟头,永远不动
let cur = dummy
while (cur.next) {
if (cur.next.val === val) cur.next = cur.next.next // 跳过
else cur = cur.next
}
return dummy.next // 真实新头
}反转链表(迭代 + 递归):
function reverse(head) { // 迭代:三指针 prev/cur/next
let prev = null, cur = head
while (cur) {
const next = cur.next
cur.next = prev
prev = cur
cur = next
}
return prev
}
function reverseR(head) { // 递归:子链反转后让原头指向新尾
if (!head || !head.next) return head
const newHead = reverseR(head.next)
head.next.next = head
head.next = null
return newHead
}环形检测(快慢指针 / 龟兔赛跑):
function hasCycle(head) {
let slow = head, fast = head
while (fast && fast.next) {
slow = slow.next
fast = fast.next.next
if (slow === fast) return true
}
return false
}找中间节点:快慢指针,快走 2 慢走 1,快到底时慢在中间。合并两个有序链表(递归最优雅):
function mergeTwo(l1, l2) {
if (!l1) return l2
if (!l2) return l1
if (l1.val < l2.val) { l1.next = mergeTwo(l1.next, l2); return l1 }
l2.next = mergeTwo(l1, l2.next); return l2
}
// 调用示例
const h = new ListNode(1, new ListNode(2, new ListNode(3)))
console.log(reverse(h).val) // 3(反转后头是 3)名词解释
- 链表(Linked List):由"节点"串成的线性结构,每个节点存数据 + 指向下一节点的指针。优点是插入/删除 O(1)(改指针即可),缺点是按下标访问 O(n)。类比:寻宝游戏,每张纸条写"下一个线索在 B 抽屉",你得顺着找。
- 虚拟头节点(Dummy Node):一个不存真实数据的"假头",放在真实链表最前面。它让"删头节点"和"删中间节点"走同一条代码路径,消灭大量边界
if。 - 快慢指针(龟兔赛跑):两个指针,一个走 1 步一个走 2 步。用于找中间点、判环——有环必相遇,因为快的总能套圈慢的。
课后练习
- 反转链表两种写法都过一遍(leetcode 206)。
- 答案:迭代版见上
reverse;递归版见reverseR。递归版先反转head.next子链得到 newHead,再让head.next.next = head把原头接成新尾。
- 答案:迭代版见上
- 找环形链表入口节点(142)的思路?
- 答案:相遇后,让一个指针从 head、另一个从相遇点同时每次走 1 步,再次相遇处即入口。这是因为 head 到入口的距离 = 相遇点到入口的距离(环内绕圈部分抵消)。
总结
链表是检验"会不会真正操作指针"的试金石。数组靠下标随机访问,链表靠 next 指针逐节游走,这个差别决定了它所有经典题的套路:反转靠三指针改写 next,判环靠快慢指针,合并靠递归。初学者最容易栽在"插入时先接后断的顺序"——一旦先断 prev.next 就丢了后半截链表,再也找不回来。虚拟头节点是我强烈推荐的"偷懒"技巧:它把头节点的特殊边界彻底抹平,让你只用一套循环处理所有情况。学链表千万别裸写代码,先在纸上画出节点和指针箭头,再翻译箭头变化成代码,正确率立刻翻倍。链表题做熟了,你对"引用/指针"的理解会上一个台阶,后面学树、图都更顺。