链表常见操作全解析:从删除节点到反转、快慢指针,一篇搞定!

57 阅读5分钟

前言

最近把链表相关的高频题又捋了一遍,感觉很多操作其实都围绕着几个核心技巧:dummy 哨兵节点头插法快慢指针。这些技巧一旦掌握,链表题基本就无敌了。

我把自己的笔记整理了一下,顺便加了一些细节和心得,分享给大家。内容从最基础的删除节点开始,逐步到反转、判环、删除倒数第 N 个节点,由浅入深,代码都用 JavaScript 写的


1. 删除链表中的某个值(LeetCode 203 移除链表元素)

最常见的操作之一:给一个值,把链表中所有等于这个值的节点删掉。

一开始我总是忘了考虑“要删的是头结点”的情况,因为头结点没有前一个节点,处理起来很麻烦。

解决办法:加一个 dummy 哨兵节点

function remove(head, val) {
  // dummy 节点的值无所谓,next 指向原头结点
  const dummy = new ListNode(0, head);
  let cur = dummy;  // 从 dummy 开始遍历
  
  while (cur.next) {
    if (cur.next.val === val) {
      cur.next = cur.next.next;  // 删除下一个节点
    } else {
      cur = cur.next;  // 正常前进
    }
  }
  return dummy.next;  // 返回新头结点
}

为什么加 dummy 这么好用? 它统一了所有节点的删除逻辑:每个要删的节点都有“前一个节点”(哪怕原来是头结点)。以后看到要修改头结点的操作,第一时间就该想到 dummy。

小 tip:很多链表题的标配就是先 new 一个 dummy,养成习惯就行。


2. 链表反转(LeetCode 206 反转链表)—— 头插法的经典应用

反转链表是我觉得最优雅的题目之一,而头插法就是它的灵魂。很多同学一看到反转就头大,其实掌握了头插法之后,反转、局部反转、甚至一些排序题都能轻松应对。

什么是头插法?

头插法(Head Insertion)是一种构建链表的方式:每次把新节点插入到链表的最前面,而不是尾部。

在反转链表里,我们正是利用这个思路:把原链表的节点一个一个“摘下来”,然后用头插法插入到一个新的链表中,自然就实现了反转。

为什么需要 dummy?

我们用一个 dummy 节点作为新链表的“假头”。它的 next 永远指向当前已经反转好的部分的最前面(即新链表的头结点)。这样操作起来特别方便,不用每次都去判断新链表是不是空的。

代码实现

function reverseList(head) {
  const dummy = new ListNode(0);  // dummy.next 始终指向当前反转好的头
  let cur = head;
  
  while (cur) {
    const next = cur.next;     // 1. 先保存下一个要处理的节点(防止断链)
    cur.next = dummy.next;     // 2. 当前节点指向已反转部分的头(插入到最前面)
    dummy.next = cur;          // 3. 更新 dummy.next,让当前节点成为新头
    cur = next;                // 继续处理原链表的下一个节点
  }
  return dummy.next;
}

手推一遍过程(链表 1→2→3→4)

  • 初始状态: 原链表:1→2→3→4→null dummy → null
  • 处理 1: next = 2 1 → null(断开原连接) 1 → dummy.next(null) dummy → 1 → null cur = 2
  • 处理 2: next = 3 2 → 1 dummy → 2 → 1 → null cur = 3
  • 处理 3: dummy → 3 → 2 → 1 → null
  • 处理 4: dummy → 4 → 3 → 2 → 1 → null

最终返回 dummy.next,就是反转后的链表。

头插法的核心三行代码(一定要背下来!)

const next = cur.next;     // 保存后继
cur.next = dummy.next;     // 指向当前新头的下一个(插入)
dummy.next = cur;          // 更新新头
cur = next;                // 继续前进

这三行几乎是所有头插法操作的模板,后续遇到局部反转(比如 LeetCode 92 反转链表 II)、按区间反转、甚至重排链表(143)等题,都可以直接套用。

掌握了头插法,你会发现反转不再是难题,而是变成了一种“乐趣”。

3. 判断链表是否有环(LeetCode 141 环形链表)

这个经典得不能再经典:快慢指针

  • 慢指针每次走 1 步
  • 快指针每次走 2 步

如果有环,快指针总会追上慢指针(在环里绕圈)。

function hasCycle(head) {
  let slow = head;
  let fast = head;
  
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      return true;  // 相遇了,肯定有环
    }
  }
  return false;  // fast 先到末尾,无环
}

为什么一定能相遇? 想象操场跑步,快的总会套圈追上慢的。数学上可以用“鸽巢原理”解释,但直觉就够用了。


4. 删除链表的倒数第 N 个节点(LeetCode 19 删除链表的倒数第 N 个节点)

这题结合了 dummy + 快慢指针,堪称链表技巧大合集。

思路:让快指针先走 n 步,然后快慢一起走,当快指针到末尾时,慢指针正好在倒数第 n 个节点的前一个。

var removeNthFromEnd = function(head, n) {
  const dummy = new ListNode(0, head);
  let left = dummy;
  let right = head;  // 注意这里可以直接从 head 开始
  
  // 快指针先走 n 步
  while (n--) {
    right = right.next;
  }
  
  // 一起走,直到快指针到末尾
  while (right) {
    left = left.next;
    right = right.next;
  }
  
  // left 现在指向倒数第 n 个节点的前一个
  left.next = left.next.next;
  
  return dummy.next;
};

为什么 right 从 head 开始,而 left 从 dummy? 因为我们要删除节点,需要操作它的前一个指针。加 dummy 保证即使删除的是原头结点也没问题。

这题一次性遍历,时间复杂度 O(L),非常优雅。


总结:链表题的三大神器

  1. dummy 哨兵节点 → 解决头结点特殊处理问题,几乎所有修改链表结构的题都推荐加一个。
  2. 头插法(三行核心代码) → 反转、局部反转、重新排序等操作的神器,记住了就能秒很多题。
  3. 快慢指针 → 环检测、找中间节点、删除倒数第 n 个……凡是跟“相对位置”有关的,都可以考虑。

掌握了这三个技巧,链表题基本就从“怕”变成了“喜欢”。下次再刷链表相关题目的时候,先问自己三个问题:

  • 要不要加 dummy?
  • 能不能用头插法?
  • 能不能用快慢指针?

答完这三个问题,代码基本就出来了。

微信图片_20251207162456_36_93.jpg