Java 实例 – 删除链表中的元素(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程中,链表是一种常见的数据结构,因其灵活性和动态特性被广泛应用于算法设计和实际开发场景。然而,对于许多初学者而言,链表的操作尤其是删除元素时的指针管理,常常成为理解的难点。本文将以“Java 实例 – 删除链表中的元素”为核心,通过分步骤讲解、代码示例和形象化比喻,帮助读者系统掌握这一知识点。无论是处理头节点、中间节点还是尾节点的删除,本文都将提供清晰的逻辑框架和实操指南。


链表基础:理解“火车车厢”模型

链表的核心思想是通过节点(Node)串联数据,每个节点包含数据域和指向下一个节点的指针。我们可以将链表想象为一列火车:每个车厢(节点)装载货物(数据),并通过轨道(指针)连接前后车厢。删除某个车厢的操作,本质上就是调整前后车厢的轨道指向。

节点的结构

链表的基本单元是节点,其 Java 实现如下:

class ListNode {  
    int val;  
    ListNode next;  
    ListNode(int val) {  
        this.val = val;  
        this.next = null;  
    }  
}  
  • val:存储具体数值
  • next:指向下一个节点的引用

链表的三大操作场景

删除链表元素时,需考虑三种典型场景:

  1. 删除头节点:类似移除火车第一节车厢,需调整头指针指向下一节车厢。
  2. 删除中间节点:需要同时调整前驱节点的指针,使其跳过目标节点。
  3. 删除尾节点:需遍历链表找到前驱节点,断开其与尾节点的连接。

删除操作详解:从简单到复杂

场景一:删除头节点

问题描述:给定链表 1 -> 2 -> 3 -> null,删除头节点 1,结果应为 2 -> 3 -> null

关键步骤

  1. 判断头节点是否为空(避免空指针异常)。
  2. 将头指针 head 指向原头节点的 next

代码实现

public ListNode deleteHead(ListNode head) {  
    if (head == null) return null; // 空链表直接返回  
    return head.next; // 头指针指向下一节点  
}  

比喻
这如同火车司机发现第一节车厢故障,直接驾驶第二节车厢继续行驶,无需处理其他车厢。


场景二:删除中间节点

问题描述:删除链表 1 -> 2 -> 3 -> 4 -> null 中的节点 2,结果应为 1 -> 3 -> 4 -> null

难点分析
需要找到目标节点的前驱节点,这要求遍历链表直到找到满足 current.next.val == target 的节点。

代码实现

public ListNode deleteNode(ListNode head, int target) {  
    if (head == null) return null;  
    // 删除头节点的特殊处理  
    if (head.val == target) {  
        return head.next;  
    }  
    // 寻找前驱节点  
    ListNode prev = head;  
    while (prev.next != null && prev.next.val != target) {  
        prev = prev.next;  
    }  
    // 调整指针  
    if (prev.next != null) {  
        prev.next = prev.next.next;  
    }  
    return head;  
}  

关键逻辑解释

  • 前驱节点定位:通过循环 prev.next 的值是否为目标值,找到目标节点的前驱。
  • 指针跳过目标节点prev.next = prev.next.next 直接将前驱的 next 指向目标节点的 next

场景三:删除尾节点

问题描述:删除链表 1 -> 2 -> 3 -> null 中的尾节点 3,结果应为 1 -> 2 -> null

挑战
尾节点的 next 指向 null,因此需要找到其前驱节点(即第二个节点),并将其 next 设为 null

代码实现

public ListNode deleteTail(ListNode head) {  
    if (head == null || head.next == null) {  
        return null; // 空链表或仅剩头节点  
    }  
    ListNode prev = head;  
    while (prev.next.next != null) {  
        prev = prev.next; // 停留在倒数第二个节点  
    }  
    prev.next = null;  
    return head;  
}  

效率优化
此方法需遍历链表,时间复杂度为 O(n)。若需频繁操作尾节点,可考虑使用双向链表或维护尾指针。


进阶技巧:综合案例与异常处理

案例:删除链表中所有指定值的节点

问题描述:删除链表 1 -> 2 -> 3 -> 2 -> 4 -> null 中所有值为 2 的节点,结果应为 1 -> 3 -> 4 -> null

解决方案

  1. 使用虚拟头节点(Dummy Head)简化操作,避免单独处理头节点。
  2. 遍历链表,跳过所有值匹配的节点。

代码实现

public ListNode removeElements(ListNode head, int val) {  
    ListNode dummy = new ListNode(0); // 虚拟头节点  
    dummy.next = head;  
    ListNode prev = dummy;  
    while (prev.next != null) {  
        if (prev.next.val == val) {  
            prev.next = prev.next.next; // 跳过目标节点  
        } else {  
            prev = prev.next; // 继续遍历  
        }  
    }  
    return dummy.next;  
}  

虚拟头节点的优势

  • 统一处理头节点与其他节点,避免额外条件判断。
  • 类似在火车头前加一个“控制车厢”,便于统一管理。

异常场景处理

1. 空链表的处理

在删除操作前,需始终检查 head == null,否则会引发 NullPointerException

2. 目标节点不存在

若链表中不存在指定值,代码需返回原链表。例如:

// 在 deleteNode 方法中,若未找到目标节点,直接返回原 head  

3. 删除唯一节点

当链表仅有一个节点且需删除时,直接返回 null 即可。


常见问题与解决方案

Q1:删除节点时为何需要前驱节点?

A
因为链表节点本身不保存前驱指针,若直接删除当前节点(如 current = current.next),会丢失对前驱节点的引用,导致无法修改其 next 指针。

Q2:如何避免删除操作中的无限循环?

A
在遍历链表时,需设置终止条件。例如:

while (prev.next != null && prev.next.val != target) { ... }  

若未找到目标节点,需提前退出循环。


总结与实践建议

通过本文的讲解,我们系统梳理了链表删除操作的核心逻辑,包括头节点、中间节点和尾节点的处理方法,以及进阶场景如删除所有匹配节点的技巧。掌握这些内容后,建议读者通过以下方式巩固知识:

  1. 手动画图模拟:用纸笔模拟链表指针变化,加深对操作的理解。
  2. 编码练习:在 LeetCode 等平台尝试题目如 203. 移除链表元素
  3. 对比其他数据结构:思考链表与数组、栈、队列在删除操作上的差异。

链表的删除操作看似复杂,但通过分步骤拆解和反复练习,其逻辑将逐渐清晰。掌握这一技能后,读者可进一步探索更复杂的链表算法,如反转链表、合并有序链表等,逐步提升算法设计能力。


通过本文的“Java 实例 – 删除链表中的元素”实践,希望读者能建立起对链表操作的系统认知,并在未来开发中灵活应用这一基础且重要的数据结构。

最新发布