删除链表元素相关的练习

本文介绍了四种链表操作问题,包括移除具有特定值的节点、删除排序链表中的重复元素、删除原始链表中的重复数字以及删除链表的倒数第N个节点。每种操作都提供了C语言的代码实现,强调了处理特殊情况和优化算法的重要性。
摘要由CSDN通过智能技术生成

目录

一、移除链表元素

二、删除排序链表中的重复元素

三、删除排序链表中的重复元素 ||

四、删除链表的倒数第 N 个结点



一、移除链表元素

给你一个链表的头节点 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

输出:[]

提示

  • 列表中的节点数目在范围 [0, 10^4]

  • 1 <= Node.val <= 50

  • 0 <= val <= 50

代码实现一

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* pre = NULL;
    struct ListNode* cur = head;
    while (cur != NULL)
    {
        if (cur->val == val)
        {
            if (pre == NULL)  // 当删除的是首元结点时,要做特殊处理,避免访问空指针
            {
                head = cur->next;
                free(cur);
                cur = head;  // 切不可写成 cur = cur->next
            }
            else
            {
                pre->next = cur->next;
                free(cur);
                cur = pre->next;  // 切不可写成 cur = cur->next
            }
        }
        else
        {
            pre = cur;
            cur = cur->next;
        }
    }
    return head;
}

free(cur); cur = cur->next 是一种类似刻舟求剑的典型的错误写法

代码实现二

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* newhead = NULL;
    struct ListNode* tail = NULL;
    struct ListNode* cur = head;
    while (cur != NULL)
    {
        if (cur->val != val)
        {
            if (newhead == NULL)  // 如果新链表为空
            {
                newhead = cur;
                tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = cur;
            }
            cur = cur->next;
        }
        else
        {
            struct ListNode* tmp = cur;
            cur = cur->next;
            free(tmp);
        }
    }
    if (tail != NULL)  // 避免对空指针的解引用
    {
        tail->next = NULL;  
    }
    return newhead;
}

将值不是 val 的结点尾插到新链表中由于尾插需要从头指针出发顺链找到尾结点,时间复杂度高,因此可以用一个尾指针 tail 来记录并更新尾结点的位置

图解示例一

 


二、删除排序链表中的重复元素

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例 1

输入:head = [1,1,2]

输出:[1,2] 

示例 2

输入:head = [1,1,2,3,3]

输出:[1,2,3] 

提示

  • 链表中节点数目在范围 [0, 300]

  • -100 <= Node.val <= 100

  • 题目数据保证链表已经按升序排列

代码实现

struct ListNode* deleteDuplicates(struct ListNode* head) 
{
    if (head == NULL)  // 判断是否为空表
    {
        return NULL;
    }
    struct ListNode* tail = head;
    struct ListNode* cur = head->next;
    while (cur != NULL)
    {
        if (cur->val != tail->val)
        {
            // 尾插
            tail->next = cur;
            tail = cur;
            cur = cur->next;
        }
        else
        {
            // 删除重复的元素
            struct ListNode* tmp = cur;
            cur = cur->next;
            free(tmp);
        }
    }
    tail->next = NULL;
    return head;
}


三、删除排序链表中的重复元素 ||

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

示例 1

 输入:head = [1,2,3,3,4,4,5]

输出:[1,2,5]

示例 2

 输入:head = [1,1,1,2,3]

输出:[2,3]

提示

  • 链表中节点数目在范围 [0, 300]

  • -100 <= Node.val <= 100

  • 题目数据保证链表已经按升序排列

代码实现

struct ListNode* deleteDuplicates(struct ListNode* head)
{
    struct ListNode* newhead = NULL;
    struct ListNode* tail = NULL;
    struct ListNode* cur = head;
    while (cur != NULL)
    {
        // 找到第一个值不同于 cur 的结点(也可能为空)
        struct ListNode* after = cur->next;
        while (after != NULL && after->val == cur->val)
        {
            after = after->next;
        }
        // 判断 cur 是否属于有重复数字的结点
        if (cur->next == after)  // 不属于
        {
            // 尾插
            if (newhead == NULL)  // 如果新链表为空
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = cur;
            }
            cur = cur->next;
        }
        else  // 属于
        {
            while (cur != after)
            {
                struct ListNode* tmp = cur;
                cur = cur->next;
                free(tmp);
            }
        }
    }
    if (tail != NULL)
    {
        tail->next = NULL;
    }
    return newhead;
}


四、删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1

 输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

示例 2

输入:head = [1], n = 1

输出:[]

示例 3

输入:head = [1,2], n = 1

输出:[1]

提示

  • 链表中结点的数目为 sz

  • 1 <= sz <= 30

  • 0 <= Node.val <= 100

  • 1 <= n <= sz

代码实现一

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) 
{
    struct ListNode* pre_slow = NULL;
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (--n)
    {
        fast = fast->next; 
        // 由于 1 <= n <= sz,所以 fast 不可能走到空
    }
    while (fast->next != NULL)
    {
        pre_slow = slow;
        slow = slow->next;
        fast = fast->next;
    }
    if (pre_slow == NULL)
    {
        head = slow->next;
        free(slow);
    }
    else
    {
        pre_slow->next = slow->next;
        free(slow);
    }
    return head;
}

相关练习:链表的中间结点、链表中倒数第 k 个结点

代码实现二

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) 
{
    // 设置一个哨兵位的头结点
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next = head;
    // 计算链表的长度(包括头结点)
    int len = 0;
    struct ListNode* cur = guard;
    while (cur != NULL)
    {
        ++len;
        cur = cur->next;
    }
    // 找到倒数第 n + 1 个结点(可能是 guard)
    struct ListNode* pre = guard;
    for (int i = 0; i < len - n - 1; ++i)
    {
        pre = pre->next;
    }
    // 删除倒数第 n 个结点
    struct ListNode* tmp = pre->next;
    pre->next = tmp->next;
    free(tmp);
    // 删除哨兵位的头结点并返回 head
    head = guard->next;
    free(guard);
    return head;
}

设置哨兵位的头结点的好处是便于首元结点的处理

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值