Leetcode刷题笔记之链表篇剑指 Offer 18. 删除链表的节点

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode刷题笔记之链表篇剑指 Offer 18. 删除链表的节点相关的知识,希望对你有一定的参考价值。

😈博客主页:🐼大家好我叫张同学🐼
💖 欢迎点赞 👍 收藏 💗留言 📝 欢迎讨论! 👀
🎵本文由 【大家好我叫张同学】 原创,首发于 CSDN 🌟🌟🌟
精品专栏(不定时更新) 【数据结构+算法】 【做题笔记】【C语言编程学习】
☀️ 精品文章推荐
【C语言进阶学习笔记】三、字符串函数详解(1)(爆肝吐血整理,建议收藏!!!)
【C语言基础学习笔记】+【C语言进阶学习笔记】总结篇(坚持才有收获!)


前言

为什么要写刷题笔记
写博客的过程也是对自己刷题过程的梳理总结,是一种耗时有效的方法。
当自己分享的博客帮助到他人时,又会给自己带来额外的快乐和幸福。
(刷题的快乐+博客的快乐,简直是奖励翻倍,快乐翻倍有木有QAQ🙈)

题目内容

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点


原题链接(点击跳转)


双指针法

为了简化我们的思考过程,可以先直接将head == NULL当中单独的一种情况,这种情况可以直接返回head
后续考虑head非空链表的情况,我们可以假设在中间某一个点找到了一个值为val的节点。为了删除这个节点,我们需要让这个节点的上一个节点指向这个节点的下一个节点,也就是pre->next = cur->next。如下图所示:


但是要注意的是,当要删除的节点是第一个节点时,也就是head所指向的节点,这个时候删除节点的操作会导致链表的起始节点(头节点)发生变化,head指向的点也要改变,因此应该将head = head->next;

函数实现
//不带头结点版本
struct ListNode* deleteNode(struct ListNode* head, int val)
    //空链表直接返回
    if(head == NULL)
       return head;
    struct ListNode* prev = NULL;//prev用来记录cur前面的节点
    struct ListNode* cur = head;//cur用来遍历链表
    while(cur)
        struct ListNode* next = cur->next;
        if(cur->val == val)
            if(cur == head)//如果cur等于头节点,说明head要发生改变
                head = head->next;//此时cur前面没有节点,故prev不变
                free(cur);
                cur = next;
            
            else
                prev->next = next;//先将cur前面节点prev->next指向cur后面的节点next
                free(cur);//释放cur当前节点资源
                cur = next;//让cur等于它下一个节点
            
        
        else
            prev = cur;//1)把当前的cur节点给prev
            cur = next;//2)将cur的next给cur 1)2)prev、cur一起走起来  
        
    
    return head;//返回链表头节点

为了方便思考,我们将head == NULL单独进行处理,但是当我们将这个代码写完后,分析代码的核心部分(while循环的内容),发现head == NULL这种情况实际上也被包含进去了,所以可以将单独处理的这种情况删除。
注意:有时候, head == NULL这种情况不能删除,需要单独处理,所以这种思考方式对于我们编写代码有一定的帮助!

//不带头结点版本
struct ListNode* deleteNode(struct ListNode* head, int val)
    struct ListNode* prev = NULL;//prev用来记录cur前面的节点
    struct ListNode* cur = head;//cur用来遍历链表
    while(cur)
        struct ListNode* next = cur->next;
        if(cur->val == val)
            if(cur == head)//如果cur等于头节点,说明head要发生改变
                head = head->next;//此时cur前面没有节点,故prev不变
                free(cur);
                cur = next;
            
            else
                prev->next = next;//先将cur前面节点prev->next指向cur后面的节点next
                free(cur);//释放cur当前节点资源
                cur = next;//让cur等于它下一个节点
            
        
        else
            prev = cur;//1)把当前的cur节点给prev
            cur = next;//2)将cur的next给cur 1)2)prev、cur一起走起来  
        
    
    return head;//返回链表头节点

双指针带头优化

为了进一步简化我们思考和编写代码的过程,我们可以添加一个带哨兵位的头节点guard,这个头节点不存放任何有效数据。
那么我们在思考和编写代码的过程中,就可以不用去考虑删除的节点是否为头节点head,从而去改变head的值。可以直接用cur、prev迭代着往后走,具体的代码如下:

函数实现
//带头结点的版本
struct ListNode* deleteNode(struct ListNode* head, int val)
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next = head;
    head = guard;
    struct ListNode* prev = head,*cur = head->next;
    while(cur)
        struct ListNode* next = cur->next;
        if(cur->val == val)
            prev->next = next;
            free(cur);
            cur = next;
        
        else
            prev = cur;
            cur = next;
        
    
    head = head->next;
    free(guard);
    return head;

前两种方法的复杂度分析:
时间复杂度 O(N): N为链表长度,删除操作平均需循环 N/2 次,最差 N 次。
空间复杂度 O(1): cur, prev 占用常数大小额外空间。


递归法

递归的方法确实不容易想,我自己一开始也没想明白,看了leetcode题解后才弄明白。当我们遇到的这个题目,且明确要求要用递归的方法解决时,只能老老实实使用递归了。
如果对递归不太了解的话,也可以看下这篇文章 426,什么是递归,通过这篇文章,让你彻底搞懂递归,其中提到了递归的模板,我们看下:

public void recursion(参数0) 
    if (终止条件) 
        return;
    
    可能有一些逻辑运算
    recursion(参数1)
    可能有一些逻辑运算
    recursion(参数2)
            ……
    recursion(参数n)
    可能有一些逻辑运算

我们来定义一个递归的函数

struct ListNode* deleteNode(ListNode head, int val)

它表示的是删除链表中值等于val的结点,那么递归的终止条件就是当head等于空的时候,我们直接返回head,因为一个空的链表我们是没法删除的,也就是下面这样

if (head == NULL)
    return head;

如果head结点不等于空,并且head结点的值等于val,我们直接返回head结点的下一个结点

if (head->val == val)
    return head->next;

除了这两种情况外,也就是head不为NULLhead->val != val,我们就递归调用,从头结点的下一个开始继续上面的操作,直到删除为止。

head->next = deleteNode(head->next, val);
return head;

这一步比较难理解,有的同学可能会疑问“后面返回的head和一开始的head是同一个吗?”,我们要注意的是递归,递归,既有递的过程,也有归的过程,最终返回的head当然是一开始的head
大概是这个样子的(灵魂画手上线了~)

函数实现
//递归的版本
struct ListNode* deleteNode(struct ListNode* head, int val)
    if(head == NULL)
        return head;
    if(head->val == val)
        return head->next;
    head->next = deleteNode(head->next,val);
    return head;

细心的同学可能会发现上面递归的方法删除节点的时候并没有释放内存,所以会存在内存泄漏的问题,为此,我们的递归法也可以这样写:

//递归的版本
struct ListNode* deleteNode(struct ListNode* head, int val)
    if(head == NULL)
        return head;
    if(head->val == val)
        struct ListNode* next = head->next;
        free(head);
        return next;
    
    head->next = deleteNode(head->next,val);
    return head;

以上是关于Leetcode刷题笔记之链表篇剑指 Offer 18. 删除链表的节点的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode刷题笔记之链表篇剑指 Offer 22. 链表中倒数第k个节点

Leetcode刷题笔记之链表篇234. 回文链表

Leetcode刷题笔记之链表篇141. 环形链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇234. 回文链表