203. 移除链表元素 - 题解与详细分析

题目描述

给定一个链表的头节点 head 和一个整数 val,要求删除链表中所有满足 Node.val == val 的节点,并返回新的头节点

示例

示例 1:

复制代码
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

复制代码
输入:head = [], val = 1
输出:[]

示例 3:

复制代码
输入:head = [7,7,7,7], val = 7
输出:[]

解题思路

这个问题需要我们在单链表中删除所有特定值的节点。由于是单链表,删除节点时需要特别注意指针的操作,特别是当要删除的节点是头节点时。

关键点分析

  1. 头节点可能被删除:如果头节点的值等于目标值,我们需要更新头指针

  2. 连续删除情况:可能存在多个连续节点都需要被删除

  3. 边界情况:空链表、所有节点都需要删除等情况

算法步骤

  1. 处理空链表:如果链表为空,直接返回 NULL

  2. 处理头节点连续等于目标值的情况:使用循环处理头节点可能需要连续删除的情况

  3. 处理中间节点:使用双指针法遍历链表,删除中间等于目标值的节点

代码实现

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    // 处理空链表情况
    if(head == NULL)
    {
        return NULL;
    }

    // 处理头节点连续等于目标值的情况
    while(head && head->val == val)
    {
        struct ListNode* temp = head->next;
        free(head);      // 释放被删除节点的内存
        head = temp;     // 更新头指针
    }

    // 使用双指针处理中间节点
    struct ListNode* cur = head;     // 当前节点指针
    struct ListNode* prev = NULL;    // 前驱节点指针

    while(cur)
    {
        if(cur->val == val)
        {
            // 找到需要删除的节点
            struct ListNode* del = cur;      // 保存要删除的节点
            prev->next = cur->next;          // 前驱节点跳过当前节点
            cur = cur->next;                 // 当前指针移动到下一个节点
            free(del);                       // 释放被删除节点的内存
            continue;                        // 跳过后续指针更新
        }
        // 正常情况:移动指针
        prev = cur;
        cur = cur->next;
    }

    return head;
}

代码详解

第一部分:处理特殊情况

复制代码
if(head == NULL)
{
    return NULL;
}
  • 如果链表为空,直接返回 NULL,避免后续操作出现空指针异常

第二部分:处理头节点

复制代码
while(head && head->val == val)
{
    struct ListNode* temp = head->next;
    free(head);
    head = temp;
}
  • 使用 while 循环处理头节点可能需要连续删除的情况

  • 比如示例3中 [7,7,7,7],所有节点都需要删除

  • 每次删除当前头节点,并将头指针移动到下一个节点

  • 循环条件 head && head->val == val 确保头节点存在且值等于目标值

第三部分:处理中间节点

复制代码
struct ListNode* cur = head;
struct ListNode* prev = NULL;

while(cur)
{
    if(cur->val == val)
    {
        struct ListNode* del = cur;
        prev->next = cur->next;
        cur = cur->next;
        free(del);
        continue;
    }
    prev = cur;
    cur = cur->next;
}

双指针法的核心逻辑:

  1. cur:当前遍历的节点

  2. prev:当前节点的前一个节点

删除节点的步骤:

  1. 保存要删除的节点:del = cur

  2. 前驱节点跳过当前节点:prev->next = cur->next

  3. 当前指针移动到下一个节点:cur = cur->next

  4. 释放被删除节点的内存:free(del)

  5. 使用 continue 跳过正常的指针更新

正常遍历的步骤:

  1. 前驱指针移动到当前位置:prev = cur

  2. 当前指针移动到下一个位置:cur = cur->next

复杂度分析

  • 时间复杂度:O(n),其中 n 是链表的长度,需要遍历整个链表一次

  • 空间复杂度:O(1),只使用了常数级别的额外空间

其他解法思路

方法二:虚拟头节点法

复制代码
struct ListNode* removeElements(struct ListNode* head, int val) {
    // 创建虚拟头节点
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = head;
    
    struct ListNode* cur = dummy;
    
    while(cur->next) {
        if(cur->next->val == val) {
            struct ListNode* temp = cur->next;
            cur->next = cur->next->next;
            free(temp);
        } else {
            cur = cur->next;
        }
    }
    
    struct ListNode* newHead = dummy->next;
    free(dummy);
    return newHead;
}

优点:统一处理头节点和中间节点的删除逻辑,代码更简洁

总结

这道题考察了链表的基本操作,特别是删除操作中指针的处理。关键是要注意:

  1. 头节点可能被删除,需要特殊处理

  2. 删除节点时要正确更新前后节点的指针

  3. 记得释放被删除节点的内存

  4. 考虑各种边界情况

掌握这种双指针遍历链表的方法对于解决链表相关问题非常有帮助。

相关推荐
AI_小站1 小时前
6个GitHub爆火的免费大模型教程,助你快速进阶AI编程
人工智能·langchain·github·知识图谱·agent·llama·rag
xindoo1 小时前
GitHub Trending霸榜!深度解析AI Coding辅助神器 Superpowers
人工智能·github
宁静的舞者1 小时前
Git、GitHub、Codeup(云效代码仓库)详解
git·代码仓库·codeup·云效
第一程序员1 小时前
GitHub Trending:Python数据科学工具新趋势
github
叶小鸡1 小时前
Java 篇-项目实战-苍穹外卖-笔记汇总
java·开发语言·笔记
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第22题:HashMap 和 HashSet 有哪些区别
java·开发语言·哈希算法·散列表·hash
陈佬昔没带相机2 小时前
Git Worktree: AI 编程 Agent 并行开发的秘密武器
git·agent
时空系2 小时前
第10篇:继承扩展——面向对象编程进阶 python中文编程
开发语言·python·ai编程
leo__5203 小时前
IEC 104 协议 C 语言实现
c语言·数据库
CHANG_THE_WORLD3 小时前
python 批量终止进程exe
开发语言·python