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. 考虑各种边界情况

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

相关推荐
轩情吖2 小时前
Qt常用控件之QDial和QSlider
开发语言·qt
轩情吖2 小时前
Qt多元素控件之QListWidget
开发语言·前端·c++·qt·控件·qlistwidget·桌面级
Getgit2 小时前
在 VS Code 中配置 PHP 开发环境完整指南
开发语言·vscode·php·intellij-idea·database
无风听海2 小时前
C# 中的 LinkedList
开发语言·c#
测试_AI_一辰2 小时前
Agent & RAG 测试工程 02:RAG 从最小闭环到可信
开发语言·前端·人工智能·github·ai编程
tudficdew2 小时前
C++中的策略模式实战
开发语言·c++·算法
naruto_lnq2 小时前
实时语音处理库
开发语言·c++·算法
程序员良辰2 小时前
JDK 环境变量的核心作用 ? 如果使用 IDEA 运行程序,是否可以不配置环境变量 ?
java·开发语言·intellij-idea
独自破碎E2 小时前
【数组】分糖果问题
java·开发语言·算法