《代码随想录》4——链表

目录

1.链表的定义

2.移除链表元素

3.反转链表

4.删除倒数第n个节点

5.环形链表

思考:(1)判断链表是否有环

            (2)如何寻找环的入口

6.总结


 

1.链表的定义

// 单链表
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr){} // 节点的构造函数(不写也会默认生成)
};

2.移除链表元素

lc.203移除链表元素

一般分为两步:

(1)改变欲移除节点的前一个节点的指针(2)释放掉移除节点的内存空间

思路1:根据节点种类的不同我们可以将节点分为头节点和一般节点,一般节点的查找方式是通过前一个节点的指针来查找,而头结点没有前一个节点,所以此方法需要特殊考虑移除头结点的情况

思路2:思考更一般化的方式,建立哨位结点作为链表头结点的前驱节点,这样无论是头结点还是一般节点,每次查找特定值val时都是通过前一个节点的指针

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode *t=new ListNode();     // 使用构造函数建立哨位结点
        t->next=head;                   // 将哨位结点与链表连接起来
        ListNode *cur=t;
        while(cur->next!=NULL)
        {
            if(cur->next->val==val)     // 查找数据域为特定值val的前一个节点
            {
                ListNode *tmp=cur->next;  // 临时变量tmp保存移除的节点
                cur->next=cur->next->next;// 改变前一个节点的指针域
                delete tmp;               // 释放移除节点的内存空间
            }
            else cur=cur->next;

        }
        
        return t->next;                    // t是我们的哨位结点,链表真正的头节点是t->next

    }
};

3.反转链表

lc.206反转链表

思考:不能申请额外的内存空间,反转链表,则只需改变整个链表所有的指针方向即可

双指针法

最终状态示意图ce253359abb24296a41f454d1f8f495b.jpeg

 

 

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *tmp,*cur=head,*pre=NULL; // 初始化cur指针为头节点,pre指针指向空
        while(cur)                         // 以cur指针遍历整个链表
        {
            tmp=cur->next;                 // 定义临时变量tmp来保存cur下一个节点   
            cur->next=pre;                 // 反转链表指针,将当前节点的指针指向前一个节点
            pre=cur;                       // 以cur指针来更新pre指针
            cur=tmp;                       // 更新cur指针指向最初cur指向的下一个节点
        }
        return pre;

    }
};

4.删除倒数第n个节点

lc.19删除链表的倒数第N个节点

思考:

(1)考虑到删除操作,我们上面发现使用哨位结点的方法更简洁

(2)思考如何定位到倒数第n个节点?

(3)采用双指针法

双指针法的思考:

 第一步:快指针先走n步

 第二步:快慢指针同时前进,直到快指针走到链表的尽头即指向空时结束

 第三步:由于最开始时快慢指针相差n步,当快指针走到空时,此时慢指针的指向刚好是倒数第n个节点

 第四步:考虑到删除操作应该对删除节点的前一个节点的指针域进行操作,所以需将慢指针指向倒数第n个节点的前一个节点,所以快指针应该比慢指针再多走一步

解法:

(1)定义哨位结点,对快慢指针进行初始化

(2)快指针先走n+1步

(3)快慢指针同时移动,直至快指针指向空

(4)更改慢指针的指针域,释放慢指针下一个节点的内存空间

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {

            ListNode *h=new ListNode(0);  // 建立哨位结点
            h->next=head;                 // 哨位结点连接链表   
            ListNode *fast=h;
            ListNode *slow=h;
            n++;                          // 快指针先走n+1步
            while(n--&&fast!=NULL)
            {fast=fast->next;}            // 若让快指针先走n步,再走一步的话,若n值>链表长度时,会出现对空指针的操作,执行会出错        
            
            while(fast!=NULL)
            {
                fast=fast->next;
                slow=slow->next;

            }
            slow->next=slow->next->next;
            
            return h->next;
    }
};

5.环形链表

lc.142环形链表

思考:

(1)判断链表是否有环

双指针法:定义快慢指针,令快指针一次走两步,慢指针一次走一步,如此进行下去快指针在环内以相对慢指针为一步的速度追赶慢指针,如若有环存在,则快慢指针终会相遇

(2)如何寻找环的入口

 快慢指针相遇状态示意图0c2371114cd6498384047cdd347987e8.jpeg

 思考:为什么慢指针走的路程是x+y而不是x+y+k(y+z)?

因为当快指针与慢指针相遇时,是快指针在环内领先了慢指针一圈,而慢指针的速度是快指针的一半,所以慢指针此时只走了半圈,也就意味着慢指针进入环的第一圈就会被快指针追上,所以慢指针的路程为x+y

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast=head;
        ListNode *slow=head;
        
        while(fast!=NULL&&fast->next!=NULL) // 注意:&&具有短路取值的特点,当前一个式子的值足以判断整个式子的值时不会再判断后面的式子
        {                                        //错误写法:while(fast->next!=NULL&&fast!=NULL)此时如果fast是一个空指针,对fast->next的操作实际上是对空指针的操作,执行出错
            fast=fast->next->next;          // 快指针每次走两步
            slow=slow->next;                // 慢指针每次走一步
            if(fast==slow)
            {
                ListNode *indexhead=head;   // 快慢指针相遇后快指针和头节点相遇处即为环的入口
                ListNode *indexfast=fast;
                while(indexhead!=indexfast)
                {
                    indexhead=indexhead->next;
                    indexfast=indexfast->next;
                }
                return indexhead;
            }
        }
        return NULL;
    }
    
};

 

6.总结

艰难的第二次,磕磕绊绊的算是坚持了下来,由于对c++的不熟练在做题的过程中遇到了好几次的bug,所以也提醒我在学算法的同时也要推进基本的c++语法学习,双指针法的应用很重要,最近很忙事情很多,希望下次的更新不会太久!5933ed227b914cb38ee080064edd9861.jpeg

 

 

  • 13
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值