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

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

相关推荐
shylyly_32 分钟前
C++中的类型转换
开发语言·c++·匿名对象·隐式类型转换·拷贝优化
tokenKe42 分钟前
Buzz:蜂巢意识协作平台|Agent 不是 Bot,而是持有独立密钥对的团队成员|SSP Github Daily
github
北冥you鱼1 小时前
Go 语言新手扫盲:指针 * 和 & 使用场景详解
开发语言·后端·golang
cui_ruicheng1 小时前
Python数据分析(一):数据分析概述与环境搭建
开发语言·python·数据分析
青山木1 小时前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先
未来之窗软件服务1 小时前
计算机考试-快速排序—东方仙盟
数据结构·算法·排序算法·仙盟创梦ide·东方仙盟
心平气和量大福大1 小时前
C#-WPF-UserControl-生命周期(加载 退出)
开发语言·c#·wpf
小龙报1 小时前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法
wWYy.2 小时前
数据结构:跳表
数据结构
sunywz2 小时前
【c#】 Web Deploy一键发布,IIS部署全流程
开发语言·前端·c#