【leetcode】203. 移除链表元素

文章介绍了两种从链表中删除特定值元素的方法,一种是直接修改原链表,另一种是构建新链表。在处理头节点删除和尾节点删除的特殊情况时,提出了使用二级指针和带哨兵头节点的链表结构。此外,还展示了如何利用哨兵头节点简化合并两个有序链表的过程。
摘要由CSDN通过智能技术生成

题目描述 

思路1: 从头遍历链表,遇到数据域为val的节点删除,链接其前一个节点和后一个节点

特殊情况讨论:

 1️⃣空链表

如示例2:无论val的值是多少,都返回空链表

2️⃣头节点的数据值等于val

此时需要更改头节点,所以参数应当设置为二级指针,但是本题当中函数的参数及返回类型已给定,要求返回新的头节点,所以如果更改头节点则更新head,返回新的链表头即可,这也是避免使用二级指针的一种方法,下文还会讲解另一种避免使用二级指针方法:使用带哨兵头的链表

3️⃣尾节点的数据值等于val

对于尾节点的数据值等于val的情况,其实和非头节点的的处理情况一样,当删除最后一个节点时,prev->next = cur->next ==NULL,因此删除节点可以分为头节点删除和非头节点删除

以下为参考代码:

struct ListNode* removeElements(struct ListNode* head, int val){
    //非空链表
    struct ListNode* cur = head;
    struct ListNode* prev = head;
    while(cur)
    {
        //删除节点
        if(cur->val == val)
        {
            //头删
            if(cur == head)
            {
                head = head->next;
                free(cur);
                cur = head;
            }
            //非头删
            else
            {
                prev->next = cur->next;//链接
                free(cur);//释放
                cur = prev->next; 
            }   
        }
        else
        {
            prev = cur;
            cur = cur->next;            
        }
    }
    return head;//返回链表头 
}

思路二:定义一个新链表,将不是数据值val的节点尾插到新链表

如上图,迭代直到cur == NULL 

📖Note

1️⃣这里的尾插指的是整个节点的插入,cur指向要插入的节点,和单链表中尾插同理,cur相当于我们新建立的一个节点,将其插入newhead指向的链表即可

2️⃣newhead要分空链表和非空链表两种情况讨论,具体步骤参考单链表尾插操作

3️⃣原链表每个节点判断或尾插后,要进行空间释放,否则会造成内存泄漏问题

4️⃣尾插结束后,tail指向新链表的最后一个节点,需要将tail->next置空

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;
    struct ListNode* tail = newhead;
    while (cur)
    {
        if (cur->val != val)
        {
            //第一个节点插入
            if (tail == NULL)
            {
                newhead = tail = cur;
                            
            }
            else
            {
                tail->next = cur;
                tail = tail->next;//更新tail
                
            }
            cur = cur->next;
            
        }
        else
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
            
        }
    }
    if (tail)
    {
        tail->next = NULL;
    }
    return newhead;
}

 以下介绍一种特殊的链表结构:带哨兵头的链表


以下为带哨兵头和不带哨兵头链表的对比

带哨兵头的链表:即给链表的头节点之前增加一个节点,这个节点的数据域不存储任何有效数据,其指针域存放的是指向链表头节点的指针head

带哨兵头节点链表的优势:

1️⃣对于单链表的操作,我们每次都需要分空链表和非空链表两种情况讨论,但当存在带哨兵头链表这种结构后,我们就不需要考虑空链表的情况

对于空链表,其带哨兵头后结构如下:

2️⃣除此之外,一些需要更改链表头节点的操作,如头插头删等,不需要我们给函数传二级指针,因为此时修改的是guard->next,即修改的是结构体中的成员变量,我们只需要结构指针guard即可

以下我们使用带哨兵头节点的链表实现移除链表元素:

我们定义一个带哨兵头节点的新链表newhead,带哨兵头之后,newhead尾插就不需要分空链表和非空链表两种情况,直接尾插即可

📖Note

  • 需要创建一个新的节点为guard,guard不能定义为NULL,否则就会发生空指针的访问
  • 函数的返回值为guard->next,即指向链表头节点的指针
struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail = guard;
    struct ListNode* cur = head;
    while (cur)
    {
        if (cur->val != val)
        {
            tail->next = cur;
            tail = tail->next;//更新tail
            cur = cur->next;
        }
        else
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
        }
        
    }
    tail->next = cur;//最后一个节点等于val等情况
    return guard->next;
}

如下合并两个有序链表,返回新的升序链表,使用带哨兵头节点的链表更方便

题目分析:

对于这两个升序链表,它们都满足前一个节点一定小于后一个节点,所以只需要依次比较这两个链表中的值,将较小的值尾插到新链表,直到所有元素比较结束,返回新链表的头节点指针即可,以样例一为例,具体步骤如下:

 📖Note

  • 新链表的尾插需要考虑第一个节点尾插的情况,但当新链表带有哨兵头节点时,不需要分情况讨论
  • 两个节点的数据比较,取较小值尾插到新链表,当两个节点中的数据相等时,我们先将L1中的数据尾插,判断条件为(cur1->val) <= (cur2->val),尾插cur1指向的节点
  • 当L1和L2有一个遍历结束,即cur1==NULL 或cur2==NULL 成立时,不再进行比较,将tail链接到非空指针所指向的位置即可

参考代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next = NULL;
    struct ListNode* tail = guard;

    while (cur1 && cur2)
    {
        //cur1尾插
        if (cur1->val <= cur2->val)
        {
            tail->next = cur1;
            cur1 = cur1->next;//更新cur1
        }
        //cur2尾插
        else
        {
            tail->next = cur2;
            cur2 = cur2->next;//更新cur2   
        }
        tail = tail->next;//更新tail
    }
    //cur1和cur2中有一个为空
    if (cur1)
    {
        tail->next = cur1;
    }
    if (cur2)
    {
        tail->next = cur2;
    }

    return guard->next;

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值