Java 实例 – 删除链表中的元素(超详细)

Java 实例 – 删除链表中的元素

在 Java 的数据结构世界里,链表是一种非常基础但又极其重要的结构。它不像数组那样需要连续的内存空间,而是通过节点之间的指针连接,实现动态存储。当我们面对需要频繁增删操作的场景时,链表的优势就凸显出来了。而“删除链表中的元素”正是链表操作中最常见、最核心的实战之一。

这篇文章,我们就以一个真实开发场景为背景,一步步带你掌握 Java 实例 – 删除链表中的元素。无论是刚接触数据结构的初学者,还是有一定经验的中级开发者,都能从中学到实用技巧。


什么是链表?它的结构如何理解?

在深入删除操作之前,先让我们理清链表的基本构成。你可以把链表想象成一列火车:每一节车厢(节点)都包含两个部分——一个是乘客(数据),另一个是连接下一辆车厢的“接口”(指针)。

在 Java 中,我们通常用一个 Node 类来表示这个车厢:

public class ListNode {
    int val;           // 节点存储的值,比如 3、5、8
    ListNode next;     // 指向下一个节点的引用,如果为空,表示这是最后一节车厢

    // 构造函数:创建一个节点并指定值
    public ListNode(int val) {
        this.val = val;
        this.next = null;  // 新建节点默认没有下一个节点
    }
}

这个 ListNode 类就是链表的“砖块”。多个 ListNode 通过 next 指针连接起来,就形成了链表。


删除链表中的元素:核心思想

删除一个节点,本质上是“让前一个节点跳过它”,也就是修改指针的指向。这就像你在火车上想让某节车厢“消失”:你只需要把前一节车厢的连接线,直接接到下下节车厢上即可。

关键点

  • 不能直接“删除”节点,因为 Java 不支持手动释放内存。
  • 你只能通过“断开连接”,让垃圾回收器自动回收。

举个例子:链表是 1 → 2 → 3 → 4,我们要删除值为 3 的节点。

步骤如下:

  1. 找到值为 3 的节点(即 node3)。
  2. 找到它的前一个节点(即 node2)。
  3. node2.next 指向 node3.next(也就是 4)。
  4. 这样 node3 就从链表中“断开”了,后续无法访问。

删除指定值的节点(基础版)

下面是一个完整的 Java 实例,实现删除链表中所有等于给定值的节点。

public class LinkedListDeletion {
    // 删除链表中所有等于 val 的节点
    public ListNode removeElements(ListNode head, int val) {
        // 1. 如果链表为空,直接返回 null
        if (head == null) {
            return null;
        }

        // 2. 创建一个虚拟头节点(哑节点),避免处理头节点的特殊情况
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        // 3. 用指针 current 指向虚拟头节点,用于遍历
        ListNode current = dummy;

        // 4. 遍历链表,直到 current.next 为 null
        while (current.next != null) {
            // 如果下一个节点的值等于目标值 val
            if (current.next.val == val) {
                // 将当前节点的 next 指向下一个节点的下一个节点
                // 相当于“跳过”了当前要删除的节点
                current.next = current.next.next;
            } else {
                // 如果不等于 val,继续移动指针
                current = current.next;
            }
        }

        // 5. 返回虚拟头节点的下一个节点,即真正的头节点
        return dummy.next;
    }
}

代码注释详解

  • dummy 是一个“虚拟头节点”,它的作用是统一处理头节点删除的情况。比如,如果原始头节点就是 val,没有虚拟头节点,你会很难处理。
  • current.next.val == val:判断下一个节点是否是目标节点。
  • current.next = current.next.next:这才是“删除”的关键操作——跳过目标节点。
  • 最后返回 dummy.next,因为 dummy 本身不是真实数据节点。

特殊情况处理:删除头节点

如果你直接删除链表的第一个节点,没有虚拟头节点的话,代码会变得非常复杂。例如:

// ❌ 危险写法(不推荐)
public ListNode removeFirst(ListNode head, int val) {
    // 如果头节点就是要删除的值
    if (head.val == val) {
        return head.next;  // 直接返回下一个节点
    }

    // 否则遍历后续节点
    ListNode current = head;
    while (current.next != null) {
        if (current.next.val == val) {
            current.next = current.next.next;
            break;
        }
        current = current.next;
    }
    return head;
}

这个方法虽然能工作,但需要额外判断头节点。而使用虚拟头节点的版本,所有情况都统一处理,代码更简洁、更安全。


删除链表中第 k 个元素(按位置删除)

有时候我们不是按值删除,而是按位置删除,比如“删除第 3 个节点”。

public ListNode removeKthFromEnd(ListNode head, int k) {
    // 1. 创建虚拟头节点,统一处理
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    // 2. 定义两个指针:fast 和 slow
    ListNode fast = dummy;
    ListNode slow = dummy;

    // 3. 让 fast 指针先走 k 步
    for (int i = 0; i < k; i++) {
        fast = fast.next;
    }

    // 4. fast 和 slow 同时移动,直到 fast 到达末尾
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }

    // 5. 此时 slow 指向要删除节点的前一个节点
    slow.next = slow.next.next;

    // 6. 返回真实头节点
    return dummy.next;
}

核心思想:快慢指针法。快指针先走 k 步,然后两个指针一起走,当快指针到达末尾时,慢指针正好在倒数第 k 个节点的前一个位置。

这个技巧在很多链表题目中都非常实用,比如“反转链表”、“环形链表检测”等。


链表删除操作的常见错误与调试建议

在实际编码中,初学者容易犯几个典型错误:

常见错误 问题原因 正确做法
忘记处理空链表 head 为 null 时直接访问 head.next 会抛空指针异常 入口先判断 head == null
没有使用虚拟头节点 删除头节点时逻辑混乱 使用 dummy 节点统一处理
指针移动顺序错误 先移动 current 再操作 next,导致丢失节点 先操作 next,再移动指针
循环条件写错 while (current != null) 会漏掉最后一个节点 应该用 while (current.next != null)

建议在调试时打印链表状态,比如:

public void printList(ListNode head) {
    ListNode current = head;
    while (current != null) {
        System.out.print(current.val + " -> ");
        current = current.next;
    }
    System.out.println("null");
}

实际应用案例:学生名单管理

假设你在开发一个学生管理系统,每个学生用一个节点表示,现在需要根据学号删除某位学生。

public class Student {
    int id;
    String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

// 链表中删除指定 id 的学生
public StudentNode removeStudent(StudentNode head, int id) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    ListNode current = dummy;
    while (current.next != null) {
        if (current.next.val == id) {  // 假设 val 存的是 id
            current.next = current.next.next;
            break;
        }
        current = current.next;
    }

    return dummy.next;
}

这个例子展示了 Java 实例 – 删除链表中的元素在实际业务中的价值:灵活、高效、可扩展。


总结与学习建议

链表的删除操作看似简单,实则蕴含了指针思维、边界处理、虚拟节点等重要编程理念。掌握“删除链表中的元素”这一基本操作,是迈向高级数据结构的必经之路。

建议你:

  • 动手实现一遍代码,不要只看。
  • 用测试用例验证各种边界情况:空链表、删除头节点、删除尾节点、删除中间节点。
  • 尝试用递归方式实现删除操作,提升思维深度。

链表是 Java 编程中非常重要的一环,而“删除链表中的元素”作为其核心操作,值得你反复练习。相信通过这篇深入浅出的讲解,你已经掌握了这项关键技能。继续加油,下一个算法高手就是你!