LeetCode-环形链表、环形链表 II

一、环形链表

. - 力扣(LeetCode)

判断是否有环,使用快慢指针,开始时都指向头节点,快指针每次走两部,慢指针每次走一步,如果在走的过程中,慢指针和快指针相同(也就是快指针和慢指针指向的节点的同)那么就说明这个链表是带环链表;

原理: 

若是这个链表代换,那么快慢指针一定不会走向NULL;只会在这个链表里面循环走下去,并且当循环条件允许时都会进环;同时快指针走的每次都比慢指针多一步,若是一直走下去 ,虽说快指针开始一直在慢指针前面,但是每次快指针都多走一步,最后快指针一定会和慢指针相遇,(就像跑步速度相异的两人在环形跑道上面跑步同理,开始时快的再前面,可能一圈或是两圈之后,跑的快的一定会和跑的慢的相遇)

因此,我们可以把快慢指针相遇作为判断链表是否带环的判断准则,当快慢指针相遇,就说这个链表带环;

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
    
    ListNode* fast=head,*slow=head;

    while(fast && fast->next && slow)
    {
        fast=fast->next->next;
        slow=slow->next;
        
        if(fast==slow)
        {
            return true;
        }
    }
    return false;
}

当头节点为NULL,或者是头节点只有一个且这个节点的next为NULL时,不进入循环,直接返回false,可见这种情况也能兼容。

当fast每次走三步或者是更多步呢?(题外话)

先看快指针每次走三步的情况:假设有环,经过上面解法可知,快慢指针相遇一定在环内部;所以我们研究在环中的过程,当慢指针进环的时候快指针已经在换里面走了一会了,我们并不清楚它走了几圈或是多远;假设环可以容纳的节点个数是C(环的长度),假设慢指针刚刚进入环的时候距离环内快指针的距离是N,那么每走一步快慢指针之间的距离就减少2,若是N是偶数,那么快慢指针就能相遇;

若是N是奇数,那么快指针在接近慢指针时相遇的距离是奇数,但是两者每次距离减少偶数2,所以快指针会追上慢指针,并且多出慢指针一个节点的距离;进入第二轮,在环中此时两者之间的距离是(C-1);若是(C-1)是偶数,那么快慢指针最终会相遇;若是(C-1)是奇数,那么会出现和第一轮相同的情况:快指针多出慢指针一个节点的距离,此时两者之间的距离又变成了(C-1);而已知(C-1)是奇数,后面的都是同样如此,这样看来似乎快慢指针永远都不会相遇;

但其实不然:

上面快慢指针不会相遇的情况出现在该条件之下:当N是奇数的时候,C是偶数;这种条件真的存在吗?假设头节点距离进环节点的距离是L,研究慢指针刚刚进环的时刻;此时慢指针走了L,假设快指针在环内走了x圈,那么快指针走的总路程就是L+x*C+N(N是慢指针刚刚进入环的时候与快指针之间的距离),同时又因为快指针走的距离是慢指针的三倍,那么就有以下等式

3*L=L+x*C+N ;

2*L=x*C+N

带入快慢指针永远不会相遇的条件:N是奇数的时候,C是偶数;

因为2*L永远是偶数,而条件带入之后,等式右边无论x是几,等式右边的最终结果都是奇数

由此可知,这种条件不会存在;当快指针每次走的步数更多得到时候,也同样如此;也就是说快慢指针一定会相遇。 


二、环形链表 II

. - 力扣(LeetCode)

 本题思路:首先判断是否带环,若是不带环就返回NULL;若是带环则返回开始进环时的那个节点指针;判断带环与否参照上一题环形链表;而对于返回进环节点指针,使用下述方法:在判断完带环与否之后我们可以得知快慢指针相遇的节点,记录下这个相遇节点,设置一次循环,让头节点从头开始遍历,相遇节点从相遇的节点开始遍历,若是两个节点相同,那么这两个节点相遇时的节点就是进环时的节点;

原理:

假设进环时的节点和头节点之间的距离是L,fast和slow指针相遇的节点距离进环节点之间的距离是N,,快慢指针相遇之后快指针在环中走了x圈,环的长度是C,把快慢指针相遇的节点称为相遇节点;找等式关系,快指针每次走两步,慢指针每次走一步,快慢指针相遇之后,快指针走的距离是慢指针的两倍,慢指针走了L+N,快指针走了L+x*C+N ;那么就有

2*(L+N)=L+x*C+N;

L+N=x*C;

L=x*C-N;

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

 得出这个关系就说明这种方法可以找到进环节点,解释:L是头节点距离进环节点的距离,头节点从头走起,相遇节点从快慢指针相遇的节点走起,两者每次都走一步,头节点走L时,相遇节点也走了L,也就是(x-1)*C+C-N;不用管(x-1)*C,因为走完(x-1)*C之后回到相遇节点,重点是C-N,我们已知相遇节点和进环节点之间的距离是N,这时已经走过的,未走过的是C-N,那么相遇节点的最后相当于走了C-N,刚好到进环节点;

所以可以利用头节点和相遇节点一起开始遍历、找出两者相同的节点的方法得出进环节点;

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode* fast=head,*slow=head;
    int flag=-1;
    
    while(fast && fast->next && slow)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(fast==slow)
       {
            flag=1;
            break;
       }
    }
    if(flag==-1)
        return NULL;

    while(head!=slow)
    {
        head=head->next;
        slow=slow->next;
    } 
    return head;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值