代码随想录算法训练营第四天| 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

24.两两交换链表中的节点

题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

思路

这道题目正常模拟就可以了。

你要想相邻两个节点交换以后整个链表的指针指向关系是什么样的

建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且注意要操作的先后顺序

初始时,cur指向虚拟头结点,然后进行如下三步:

操作之后,链表如下:

看这个可能就更直观一些了:

概述:设置虚拟头结点后正常模拟,以 head = [1,2,3,4] 为例,交换一对节点分为三步进行:

操作前链表为:dummyHead -> 1 -> 2 -> 3 -> 4 -> null

第一步直接越过下一个节点1,将当前节点 dummyHead 的 next 指向节点2;

第二步再将节点2的 next 指向节点1;

第三步将节点1的 next 指向节点3,与原链表重新连接起来。

以上三步便可将一对节点交换,操作后链表变为:dummyHead -> 2 -> 1 -> 3 -> 4 -> null

之后遍历链表重复进行交换即可。

C代码如下:

struct ListNode* swapPairs(struct ListNode* head) {

    //设置虚拟头结点,便于后续操作

    struct ListNode* dummyHead=malloc(sizeof(struct ListNode));

    dummyHead->next=head;

    //创建curr指针用于遍历链表

    struct ListNode* curr =dummyHead;

    while(curr->next && curr->next->next){

        //记录临时节点

        struct ListNode* tmp1=curr->next;

        struct ListNode* tmp2=curr->next->next->next;

        //开始交换节点(三个步骤)

        curr->next=curr->next->next;//第一步

        curr->next->next=tmp1;//第二步

        tmp1->next=tmp2;//第三步

        //curr指针移动两位,准备下一对交换

        curr=curr->next->next;

    }

    return dummyHead->next;

}

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

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

思路

要注意,要删除第N个节点,那么我们当前遍历的指针一定要指向第N个节点的前一个节点

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fastslow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

分为如下几步:

  • 定义fast指针和slow指针,初始值为虚拟头结点,如图:

fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:

fast和slow同时移动,直到fast指向末尾,如图:

删除slow指向的下一个节点,如图:

C代码如下:

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {

    //创建虚拟头结点

    struct ListNode* dummyHead=malloc(sizeof(struct ListNode));

    dummyHead->next=head;

    //创建双指针

    struct ListNode* fast=dummyHead;

    struct ListNode* slow=dummyHead;

    //fast先走n+1

    while(n--){

        fast=fast->next;

    }

    fast=fast->next; //fast再提前走一步,因为需要让slow指向删除节点的上一个节点

    //fastslow同时移动直到fast指向NULL

    while(fast){

        fast=fast->next;

        slow=slow->next;

    }

    //删除slow所指向的节点并释放结点空间

    struct ListNode* tmp=slow->next;

    slow->next=slow->next->next;

    free(tmp);

    //注意返回的是dummy->next而不是head

    return dummyHead->next;

   

}

难点:想明白fast指针到底一开始先走n步还是n+1步?

面试题02.07.链表相交

题目:给你两个单链表的头节点 headAheadB,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null

大家注意数值相同,不代表指针相同。

思路

简单来说,就是求两个链表交点节点的指针这里要注意,交点不是数值相等,而是指针相等。

概述:先分别计算出两个链表的长度并计算长度差gap,之后让较长的链表向前移动 gap 步,这样两个链表就实现了尾部对齐(即操作两个链表的指针在同一起点上)。最后遍历两个链表判断是否有交点。

C代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {

    //使用两个指针用于遍历两个链表

    struct ListNode* currA=headA;

    struct ListNode* currB=headB;

    //统计两个链表的长度

    int lenA=0,lenB=0;

    while(currA){

        lenA++;

        currA=currA->next;

    }

    while(currB){

        lenB++;

        currB=currB->next;

    }

    //统计完长度让currA,currB回到起始点

    currA = headA;

    currB = headB;

    //currA指向较长的链表

    if(lenB>lenA){

        struct ListNode* temp=currA;

        currA=currB;

        currB=temp;

       

    }

    //计算两个链表的长度差

    int gap=lenA > lenB ? lenA-lenB : lenB-lenA;

    //让两个链表尾部对齐

    while(gap--){

        currA=currA->next;

    }

    //遍历两个链表,若遇到相同说明相交,返回相交节点

    while(currA){

        if(currA==currB){

           return currA;

        }

        currA=currA->next;

        currB=currB->next;

    }

    //若无相交则返回空指针

    return NULL;

   

}

142.环形链表II

算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口

题目: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos -1,则在该链表中没有环。

思路

这道题目,不仅考察对链表的操作,而且还需要一些数学运算。

主要考察两个知识点:

  • 判断链表是否有环
  • 如果有环,如何找到这个环的入口

判断链表是否有环

可以使用快慢指针法,分别定义 fast slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast slow指针在途中相遇 ,说明这个链表有环。

为什么fast一次走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢

首先第一点:因为fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

为什么fast指针和slow指针一定会相遇呢?

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow,所以fast一定可以和slow重合。

动画如下:

https://code-thinking.cdn.bcebos.com/gifs/141.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.gif

如果有环,如何找到这个环的入口

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

1.当 slow 入环后,对 slow 来说,fast 是在追赶它,而且每次都会靠近一个节点,因此一定可以追到。

2.由于 slow 每次只走一步,且 fast 每次都会追到一步,而 fast 距离 slow(相对 fast 追 slow 来说,fast 在 slow 之后)最多不会超过环的所有结点数,因此 fast 一定会在 slow 走完环的一圈之前追到slow。

结论: 从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2

index1index2同时移动,每次移动一个节点,那么他们相遇的地方就是 环形入口的节点。

动画如下:

https://code-thinking.cdn.bcebos.com/gifs/142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II%EF%BC%88%E6%B1%82%E5%85%A5%E5%8F%A3%EF%BC%89.gif

C代码如下:

struct ListNode *detectCycle(struct ListNode *head) {

    //定义快慢指针

    struct ListNode* fast=head;

    struct ListNode* slow=head;

    //先找相遇交点,再找环形入口

    while(fast && fast->next){

        fast=fast->next->next;

        slow=slow->next;

        //快慢指针相遇 ,此时让两个指针分别从head和相遇点出发直至相遇即为环形入口

        if(fast==slow)

        {

            while(slow!=head){

                slow=slow->next;

                head=head->next;

            }

            return head;//返回环的入口

        }

    }

    return NULL;

}

写在最后:今天的几道题很锻炼脑子,感想就是做算法题,脑子常用常新。

如果某道题一时想不明白,你可以先记住这道题的关键解题点.例如这道环形链表题里如何找环形入口的结论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值