环形链表及其求法

OJ题1:https://leetcode.cn/problems/linked-list-cycle/ //判断链表是否有环

2:力扣   //返回链表开始入环的第一个节点

环形链表示例:

  

 

解决方法:

Slow和fast指向链表的开始

Slow一次走一步

Fast一次走两步

不带环,fast就会为空

带环,fast就会在环里面追上slow

问题:

  1. 为什么slow和fast一定会在环中相遇?会不会在环里面错过,永远追不上?请证明。
  2. 为什么slow走一步,fast走两步呢?能不能fast一次走n步?(n>2)?请证明。

问题1:

第一步:slow和fast,fast一定先进环,这时slow走了入环前距离的一半

第二步:随着slow进环,fast已经在环里面走了一段,走了多少跟环的大小有关系

Slow每次走一步,fast每次走两步:

假设slow进环的时候,slow和fast的距离是N,fast开始追slow

每追一次,fast和slow的距离变化:

N

N-1

N-2

N-3

……

1

0

Slow每次走一步,fast每次走两步:

每追一次,距离较2,他们之间的距离最后减到0的时候就是相遇点。

假设slow进环的时候,slow和fast的距离是N,fast开始追slow

每追一次,fast和slow的距离变化:

N------N为偶数        N--------N为奇数

N-2                   N-2

N-4                   N-4

N-6                   N-6

……                    ……

2                      1

0                      -1

可以追上              这一次追不上

N是奇数,距离变为-1,什么意思?意味着他们之间的距离变成了C-1(C是环的长度);如果C-1是奇数,那就永远追不上了;如果C-1是偶数,那么就可以追上。

问题:Slow走一步,fast走两步,一定会相遇,如何求环的入口点呢?

法一:结论:一个指针从相遇点开始走,一个指针从链表头开始走,他们会在环的入口处相遇。

证明:

在追上相遇的过程中,

慢指针走的距离:L+X

快指针走的距离:L+N*C+X  (N≥1) N是相遇前,fast在环里走的圈数

快指针走的距离是慢指针的1倍

2*(L+X)=L+N*C+X

L+X=N*C

L=N*C-X

L=(N-1)*C+C-X

其中,(N-1)*C表示从meet回到meet,C-X代表剩下的路程。

 

代码实现:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head)
 {
    struct ListNode *slow=head;
    struct ListNode *fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
          struct ListNode *mid=slow;
          struct ListNode *cur=head;
          while(mid!=cur)
          {
            cur=cur->next;
            mid=mid->next;
          }
          return cur;
          }

     }
    return NULL; 
}

法二:设相遇点的下一个点为newHead,把相遇点和下一个点断开,让相遇点指向NULL,newHead作为新链表的头,此时就被转化为相交链表的问题。

代码实现:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode *find(struct ListNode *headA, struct ListNode *headB) 
{
    struct ListNode *curA=headA;
    struct ListNode *curB=headB;
    int A=1;
    int B=1;
    while(curA->next)
    {
        curA=curA->next;
        A++;
    }
    while(curB->next)
    {
        curB=curB->next;
        B++;
    }
    if(curA==curB)
    {
        int gap=abs(A-B);
        struct ListNode *fastList=headA;
        struct ListNode *slowList=headB;
        if(A<B)
        {
            fastList=headB;
            slowList=headA;
        }
        while(gap--)
        {
            fastList=fastList->next;
        }
        while(fastList!=slowList)
        {
            fastList=fastList->next;
            slowList=slowList->next;
        }
        return slowList;

    }
    else
    {
        return NULL;
    }
}

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *slow=head;
    struct ListNode *fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
          struct ListNode *list=slow->next;
          slow->next=NULL;
          struct ListNode *cur=find(list,head);//相交链表找交点
          return cur;
        }
     }
    return NULL; 
}

法二改进:心里想的是要断开,实际实现可以不用断开,以meet作为终点也可以实现;适合题目中有要求不能断开链表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值