环形链表LeetCode经典题目讲解

目录

1.环形链表I:

2.环形链表II:

3.随机链表的复制:​编辑


1.环形链表I:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle/description/257531007eff4bd898b099dd668965ba.png

要解得此题,需要用到一个方法:快慢指针法。

       就是定义两个指针(slow指针和fast指针),在刚刚开始的时候,让这两个指针均指向链表的头,紧接着,让一个指针一次走一步slow(也就是一个节点的位置),而另一个指针一次走两步fast(也就是两个节点的位置),如果这两个人指针相遇(slow==fast),则说明此链表中有环,若fast指针为空指针(fast==NULL),或者fast指针的下一个指针(fast->next==NULL),则可以说明该链表中没有成环。

e6a1f4ad85a14a72912411fc877ae585.png

通过这道题,我们可以衍生出以下几个问题:

(1).为什么一定会相遇,有没有可能会错过。

       假设slow刚进入环时与fast的距离是N,在不断跑的过程中,fast和slow之间的距离变化如下所示:N

       N-1

       N-2

       ......              ----------->fast一次走两步,slow一次走一步,他们每走一步,fast与slow之间的            2                                  距离就会缩短一步,当N为0的时候,就追上了。

        1

        0

(2).slow每走一步,若fast每走3步,4步,5步呢?(在这里我们拿fast一次走3步为例来说明)

偶数           奇数

N                N

N-2             N-2

N-4             N-4

......             ....

2                 1

0                  -1(就是fast和slow刚好错过,如下图所示:)

698cfa822a9d411892f746882f9e961c.png

       由上图可以知晓,如果fast一次走3步的话,而slow一次走1步,那么我们就可以知道当它们两个各走一步的时候,它们之间的距离就会减少2个,因此就有了以上两种情况(N为奇数或者为偶数):

       (1).如果N为偶数,那么最后fast和slow就会相遇。

       (2).如果N为奇数,那么就会错过,这样的话,fast和slow之间的距离就有会变为(这里我们假设环的周长为C)C-1,如果C-1是偶数,那么第二次就会相遇,如果为奇数,哪么就会陷入死循环,永远无法相遇。但是他真的无法相遇吗?对于这个问题,我们不彷来探讨一下,由上图和分析我们可知,死循环发生的条件是N为奇数,C为偶数。

27389abceeb648779c5ec28e9bb44291.png

接下来我们以上面这幅图为例来分析一下:

当slow进入环时slow走过的距离是:L

此时fast走过的距离是                    :L+xC+C-N

由于时间是一样的,因此fast走过的路程是slow走过路程的3倍,

3L=L+xC+C-N;

2L=(x+1)C-N;

       等式左侧必定是偶数,因此等式右侧也必须是偶数,由此等式则可以判断出C若为偶数,N若为奇数,则等式永远不会成立,因此,我们可以得出结论,就是永远不会有死循环这种情况的发生。

       当然,运用这位上面写的内容,我们可以判断出一些其它情况(比如说fast一次走4步等等)。

无论fast走几步或者是slow走几步,最后都会相遇。

bool hasCycle(struct ListNode* head)

{

    if (head->next == NULL || head == NULL)

    {

        return false;

    }

    struct ListNode* fast = head->next;

    struct ListNode* slow = head;

    while (slow != fast&&fast->next != NULL&&fast!=NULL)

    {

        slow = slow->next;

        fast = fast->next->next;

    }

    if (fast == slow)

    {

        return true;

    }

    return false;

}

2.环形链表II:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle-ii/

0ba95a78d1894df98b5533f251a24132.png

         由题可知:这道题的目的是返回入环的第一个节点,这道题所用到的方法其实和上面的那道题是非常相似的,都用到了快慢指针法。

       这道题在刚开始解答的时候,大家在尝试做这道题的时候,会感觉到无从下手,那么,在这里,我来给大家讲一下具体的方法:先定义两个指针(fast和slow),再定义一个cur指针让其指向head指针,让它们两个和上一题一样fast指针一次走2步,slow指针一次走1步,找到它们两个相遇的那个点,定义一个meet指针,让其指向slow和fast指针相遇的地方,然后meet一次走1步,cur指针一次走1步,若cur指针和meet指针相遇,则说明它们相遇的那个地方就是入环的第一个节点的位置。说到这里,我们就会有很多的同学会产生一些疑问,这是为什么呢?那么接下来我们就这个问题来给大家解答一下这个问题。

fe157afc6b0542bbad66c0df122bff63.png

       由上图可知:假设slow和fast指针在图上的此位置相遇,那么设入环的第一个节点到相遇位置的距离是N,环的周长是C,所以,我们就可以得到以下这个表达式:

slow指针移动的距离:L+N;

fast指针移动的距离 :L+xC+N;

      由上述表达可知,fast指针移动的距离是slow指针移动距离的2倍,因此:

2(L+N)=L+xC+N;

L=(x-1)C+C-N;

        (x-1)C代表的意思是绕环转了x-1圈,而C-N就是M,换句话说,就是L=N,那么我们让cur指针和meet指针一次走1步,这样,当它们相遇的时候,就是入环的第1个节点。

struct ListNode* EntryNodeOfLoop(struct ListNode* pHead) 
{
    if (pHead->next == NULL || pHead == NULL)
    {
        return NULL;
    }
    struct ListNode* fast = pHead;
    struct ListNode* slow = pHead;
    while (fast != NULL && fast->next != NULL)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow)
        {
            break;
        }
    }
    if (fast == NULL)
    {
        return NULL;
    }
    struct ListNode* cur = pHead;
    while (cur != slow)
    {
        slow = slow->next;
        cur = cur->next;
    }
    return cur;
}


3.随机链表的复制:2caca074a50446ce83f90a3138709d24.png

       这道题属于有点难度的题,相信大多数的小伙伴在看到这道题的第一眼就感觉毫无思路,这道题如果用我们的传统思维去解答的话,使用的知识以我们现在的知识储备去做的话是解不出来的,但是我这里有一个非常好的方法,你看完之后,绝对有一种醍醐灌顶的感觉。好的,那么接下来,就是精彩的地方。       8b9e4aefb94b499f92bab763e87650b1.jpeg

上图所示就是解法,我们可以复制当前节点,然后将复制好的这个节点再重新插入到当前节点的后面,第二个节点的前面,复制后的节点的next指针指向原链表的第二个节点,random指针指向前一个节点的random节点的next的指向,以后的每一个节点均是如此。

1a176d763d404f978d5dee87bae0af93.png

8907ee2e760442118f2cb63843d36bfc.png

上述代码是复制当前位置的节点并将其放在当前节点位置的后面,一遍一遍地进行遍历。

f5e8f21a6bf844af8738387a99a9b49e.png

上述代码表达的是将每一个复制后的节点的random指针的指向均与原链表中的random指针的指向相同,复制节点的random指针指向此复制节点原节点的next指针。

87ff5b956f834474b06e3dadae92d5a6.png

此步骤是为了将那些复制的节点全部形成一个单独的链表,因为题目要求要返回一个被复制好的新链表。

       大家可以试着去做一下这道题:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/copy-list-with-random-pointer/

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node node;
struct Node* copyRandomList(struct Node* head) 
{
    node*cur=head;
    while(cur)
    {
        node*copy=(node*)malloc(sizeof(node));
        copy->next=cur->next;
        cur->next=copy;
        cur=copy->next;
    }
    cur=head;
    while(cur)
    {
        copy=cur->next;
        if(cur->next==NULL)
        {
            copy->random=NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        cur=copy->next;
    }
    node*copyhead=NULL,copytail=NULL;
    cur=head;
    while(cur)
    {
        node* copy=cur->next;
        node*next=copy->next;
        if(copytail==NULL)
        {
            copyhead=copytail=copy;
        }
        else
        {
            copytail->next=copy;
            copytail=copy;
        }
        cur->next=next;
        cur=next;
    }
}

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值