链表操作的核心技巧:Dummy 节点、快慢指针与头插法

79 阅读4分钟

链表操作的核心技巧:Dummy 节点、快慢指针与头插法

链表是一种基础但极其重要的数据结构,在算法面试和工程实践中频繁出现。然而,由于其“指针式”的连接方式,对链表进行增删改查等操作时,常常需要处理复杂的边界条件。本文将系统性地介绍三种高效、通用的链表操作技巧:Dummy(哨兵)节点快慢指针头插法反转链表,帮助你优雅地解决各类链表问题。


一、Dummy 节点:统一处理边界,简化逻辑

在链表操作中,最棘手的问题往往出现在头节点——因为它没有前驱节点。例如,删除值为 val 的节点时,如果目标恰好是头节点,就需要单独判断并返回 head.next,代码变得冗余且易错。

解决方案:引入 Dummy 节点(哨兵节点)

Dummy 节点是一个人为添加的、不存储真实数据的假节点,通常置于链表最前端。它的核心作用是:

  • 为原链表提供一个统一的“前驱”;
  • 消除对头节点的特殊处理;
  • 让所有节点的操作逻辑保持一致。

示例:删除链表中第一个值为 val 的节点

function remove(head, val) {
    const dummy = new ListNode(0);
    dummy.next = head;

    let cur = dummy;
    while (cur.next) {
        if (cur.next.val === val) {
            cur.next = cur.next.next; // 直接跳过目标节点
            break;
        }
        cur = cur.next;
    }
    return dummy.next; // 返回真正的头节点
}

无论要删除的是头节点还是中间节点,逻辑完全一致,代码简洁且健壮。


二、头插法 + Dummy 节点:优雅实现链表反转

链表反转是经典问题。常见的迭代解法容易出错,尤其是指针的更新顺序。借助 Dummy 节点 + 头插法,可以清晰、安全地完成反转。

头插法三步走

  1. 保存下一个节点:防止断链后丢失后续节点;
  2. 当前节点指向已反转部分的头
  3. 更新反转部分的新头

Dummy 节点在此充当“反转结果的容器”,其 next 始终指向当前已反转链表的头节点。

代码实现

function reverseList(head) {
    const dummy = new ListNode(0); // 哨兵节点
    let cur = head;

    while (cur) {
        const next = cur.next;       // 1. 保存下一个节点
        cur.next = dummy.next;       // 2. 当前节点插入到已反转部分的头部
        dummy.next = cur;            // 3. 更新新头
        cur = next;                  // 移动到原链表的下一个节点
    }

    return dummy.next; // 返回反转后的头节点
}

这种方法逻辑清晰,每一步都有明确目的,避免了传统三指针法中容易混淆的指针交换。


三、快慢指针:解决“倒数第 N 个”与“环检测”问题

快慢指针是处理链表位置类问题的强大工具。两个指针以不同速度遍历链表,通过它们的相对位置关系,巧妙地定位目标节点。

应用 1:判断链表是否有环

  • 快指针每次走两步,慢指针每次走一步;
  • 若存在环,快指针终将追上慢指针;
  • 若无环,快指针会先到达 null
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:删除倒数第 N 个节点

直接计算长度再定位效率低,且需两次遍历。使用快慢指针可一次遍历完成

  1. 快指针先走 N 步;
  2. 慢指针从头开始,与快指针同步前进;
  3. 当快指针到达末尾时,慢指针正好位于倒数第 N 个节点的前一个
  4. 利用 Dummy 节点确保即使删除头节点也能安全操作。
function removeNthFromEnd(head, n) {
    const dummy = new ListNode(0);
    dummy.next = head;

    let fast = dummy, slow = dummy;

    // 快指针先走 n 步
    for (let i = 0; i < n; i++) {
        fast = fast.next;
    }

    // 双指针同步移动,直到快指针到达最后一个节点
    while (fast.next) {
        fast = fast.next;
        slow = slow.next;
    }

    // 删除倒数第 n 个节点
    slow.next = slow.next.next;

    return dummy.next;
}

该方法时间复杂度 O(L),空间复杂度 O(1),且无需处理头节点删除的特殊情况。


总结

技巧核心思想典型应用
Dummy 节点添加虚拟头节点,统一操作逻辑删除节点、插入节点、反转链表
头插法将当前节点插入到结果链表头部链表反转
快慢指针利用速度差定位特定位置环检测、找中点、删除倒数第 N 个节点

掌握这三种技巧,不仅能高效解决大多数链表问题,还能写出更简洁、鲁棒、易于维护的代码。在实际编程中,建议优先考虑引入 Dummy 节点来规避边界问题,结合快慢指针和头插法,构建清晰的解题框架。

链表虽无下标,但有章法。善用这些模式,你将游刃有余于指针之间。