链表题目(链表是否带环,查找链表相交节点,返回入环第一个节点)

目录

1.链表带环

2.寻找链表相交的节点

3.返回入环的第一个节点 


所有题目来自leetcode题库

1.链表带环

141. 环形链表 - 力扣(LeetCode)

链表带环问题是经典的链表题目,基本上都出现在单链表的结构中,这个问题我觉得最快最方便的方法是使用快慢指针进行判断,如果有环,则总有一圈快指针和慢指针会相遇,无环的话快指针走到尾则会从循环中直接跳出

可是怎么判断相遇呢?会不会出现错过导致一直不会相遇的情况呢?

假设slow进入环,fast距slow的距离是N,如图:

假设slow的前进步数为1,fast为2,在slow进环后,fast会开始追逐slow,此时fast距离slow有N步,但它每次前进都会缩小这个距离因为它比slow快1步,此时N-1,然后反复循环N-1,N-1,直到N=0,fast追上了slow,然后对比两者的地址如果相等此时证明链表有环返回true

会不会错过导致一直不相遇的问题可以通过上面的图得出数学表达式:

假设slow刚入环

slow的走的距离是:L

fast的走的距离是:L+X*C+C-N

Nfast距slow的距离是N

X*C:在slow进环前可能已经转了X圈

C:圈的长度

设N为奇数,第一圈的时候错过了,此时fast应在slow前一步,此时对于fast追击slow的距离应该是C-1,所以就算圈是奇数也没关系会在第二圈追上

总结:

1.N如果是偶数,那么在第一轮就可以追上

2.N是奇数导致第一轮错过了,这时距离就会变成C-1,C-1为偶数则下一轮追上

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(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)
        {
            return true;
        }
    }
    return false;        
}

2.寻找链表相交的节点

160. 相交链表 - 力扣(LeetCode)

该题有两个单链表,他们某个节点开始是相交的,后续一致,返回相交的第一个节点。

1.通过计算两个链表的长度,然后求出两者的长度差x,然后长链表先走x步与短链表起步一致

此时两者同时前进进行地址对比,一但发现两者地址相同,返回其中一个的地址然后停止循环

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
   struct ListNode *A=headA,*B=headB;
    int a=0,b=0;
    while(A)        //计算A链表长度
    {
        A=A->next;
        a++;
    }
     while(B)
    {
        B=B->next; //计算B链表长度
        b++;
    }
    //计算长度差
    int ret=abs(a-b);    
    //判断哪个更长
    struct ListNode* pcur=headA,* ptail=headB;
     if(b>a)
     {
        pcur=headB;
        ptail=headA;
     }
     //长链表先走ret步
     while(ret--)
     {
        pcur=pcur->next;
     }
     //逐一对比
     while(pcur!=ptail)
     {

        ptail=ptail->next;
        pcur=pcur->next;
     }
     return pcur;
}

2.双指针

headA和headB的长度分别为x,y,设headA未相交的节点有m个,headB有n个,相交的节点有z个。此时m+z+n=n+z+m。

建立两个指针A,B分别代表headA,headB,两个指针同时前进,但不会同时到达尾,短的一方A先走到尾此时转换成headB继续前进,长的一方B走到尾转换成headA继续前进,此时两者起步一致,最后两个指针会同时到达两个链表相交的节点,返回相交节点

注意:带环问题的解法最后的对比循环条件应该是对比双方的地址而非值,如果是对比值可能会死循环或者返回节点值一致但是并不是相交节点的情况

对比值的问题如下图:

当两个链表起步一致后,走到都为2的节点,如果是对比值的话就会停止循环并返回其中一个的节点,但实际上并没有走到相交的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{

   if(headA==NULL||headB==NULL)
   {
        return NULL;
   }
   struct ListNode *A=headA,*B=headB;
   while(A!=B)
   {
     A=(A==NULL)?headB:A->next;
     B=(B==NULL)?headA:B->next;
   }
    return B;
}

3.返回入环的第一个节点 

142. 环形链表 II - 力扣(LeetCode)

这一题是链表带环问题的衍生问题,它需要找到入环的第一个节点并返回,如果无环则返回NULL,这里的解法可以结合上面的两个题目的代码破解

1.利用上面两个问题的代码进行解决

如图:

(一)首先先找到环的相交节点,通过链表是否带环问题可以轻松解决,找到节点后,我们可以分别创建两个新指针,新指针NEWA,NEWB分别指向为fast与slow相交的节点,及相交的下一个节点

(二)然后将环断开,也就是NEWA的节点的next不再指向下一个节点而是指向空。这时我们再往回看,这不就变成寻找链表的相交节点的问题了吗?此时的两个链表分别是NEWB为头,head为头的两个链表,调用查找交点的函数就可以轻松解决了

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{

   if(headA==NULL||headB==NULL)
   {
        return NULL;
   }
   struct ListNode *A=headA,*B=headB;
   while(A!=B)
   {
     A=(A==NULL)?headB:A->next;
     B=(B==NULL)?headA:B->next;
   }
    return B;
}
struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode*slow=head;
    struct ListNode*fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
           struct ListNode *headA=slow;
           struct ListNode *headB=headA->next;
           headA->next=NULL;
           return getIntersectionNode(head,headB);
        }
    }
    return NULL;
}

(2)通过链表带环的数学表达式假设两点相遇时

slow的走的距离是:L+N

fast的走的距离是:L+X*C+N

N:是他们入环后以环的初始节点走的距离

L:离入环的距离

设fast走的是slow的两倍可以得出2*(L+N)=L+X*C+N表达式将它化简一下得出L+N=X*C,

L=X*C-N。X*C是圈数,而fast始终都比slow快所以X!=0,但是这并没有什么太大关系,因为不管转几圈它都会回到环的起点再减去N的距离,用这个式子看长度就可以可以看成L=C-N,通过式子来进行判断,此时只要一个指针从开始的地方出发,另外一个从交点出发,他们相遇的点就是入环的第一个节点

所以可以这么写

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode*last=head;
    struct ListNode*fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        last=last->next;
        if(fast==last)
        {
           struct ListNode*meet=fast;   //建立一个从交点出发的指针
           while(meet!=head)
           {
            meet=meet->next;    
            head=head->next;    
           }
           return meet;
        }
    }
    return NULL;
}

本章内容到这里就结束了,希望这篇文章对你有帮助

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值