yo!这里是环形链表问题

目录

前言

问题介绍

解决方法

进阶问题介绍

解决方法

方法一:

方法二:


前言

        当我们学习单链表的知识点之后,必不可少就是刷关于其它的oj题,那也就一定会遇到环形链表的问题,我们直到,链表是属于线性表,是一对一的数据关系,当某一个节点的next指针指向了此节点或此节点之前的节点,此时就造成了环形链表,在遍历链表时就会死循环,在具体的开发环境中也会出现很大的问题,所以解决环形链表问题就相当的重要。

        实际上,解决这个问题所需要写的代码并不多,但是难在解决思路的理解上,而且同时也会产生一些疑问,下面我就带大家来看一下这个问题,并解决大家的疑问。

问题介绍

        环形链表问题,顾名思义,就是给你一个链表的头节点,判断此链表中是否存在环,存在返回true,不存在返回false,比如,

解决方法

        其实,解决环形链表问题的方法大家并不陌生,写过几个关于链表的oj题的同学都应该知道快慢指针,就是两个指针都指向头节点,一起向后走,快指针一次走两步(一个节点为一个步长),慢指针一次走一步,下面看具体思路。

思路过程:

        首先,链表中无环的话,快指针一定会走到最后(即指向NULL),这也是判断链表无环的关键,其次,链表中有环的话,设置快慢指针从头节点出发,主要关注下面几个关键时间点:

①慢指针在后,快指针刚进入环

②慢指针刚进入环,快指针在环中

③此时两个指针都在环中,快指针在前,慢指针在后,快指针追击慢指针 ,一定能追上(后面证明),被追上的标志就是快慢指针相等。

 

证明(为什么一定能够追上):

        从上述思路过程的时间点②说起,假设两个指针相距x个节点(如下图1),之后当慢指针一次走一步,快指针一次走两步,它们的距离为x-1(如下图2),再各自走一次,距离为x-2.......两指针每各自走一次,距离就会减一,距离一定可以减为0,此时两个指针相等,这是判断链表有环的关键。

思考 (如果快指针一次走三步,走四步.......走n步呢):

        我们知道慢指针一次走一步,快指针一次走两步,是一定可以判断链表中是否有环的,如果快指针一次走三步,从上面思路过程的关键点②开始,此时快慢指针相距x,之后当慢指针一次走一步,快指针一次走三步,它们的距离为x-2,再各自走一次,距离为x-4.......那我们发现,

1)当起始距离为偶数时,距离最后会变成0(即相等),

2)但当起始距离为奇数时,第一圈时距离不会变成0(...3,1,-1...),

那就会问了 “这一次错过去了,快指针再追一圈时会不会相遇呢,在追一圈呢” ,我们假设环的大小为c,那么当距离减为-1时,此时快慢指针的距离为c-1,

①如果c为奇数,此时c-1为偶数,那么距离最后一定会减为0,

②如果c为偶数,此时c-1为奇数,那么距离最后一定不会减为0,并且之后无论转多少圈快慢指针都不会相遇。

        这是快指针一次走三步的情况,那一次走四步,五步......n步类似都可以分析出来。

代码:

struct ListNode {
    int val;
    struct ListNode *next;
};
typedef struct ListNode Lnode;
bool hasCycle(struct ListNode *head) {
    if(!head)
    {
        return false;
    }
    Lnode* slow=head;
    Lnode* fast=head;
    while(fast)
    {
        slow=slow->next;//慢指针走一步

        fast=fast->next;//快指针走两步的第一步
        if(!fast)//判断快指针第一步之后有无走到头
        {
            break;
        }
        fast=fast->next;//快指针的第二部

        if(slow==fast)
        {
            return true;
        }
    }
    return false;
}

进阶问题介绍

        对于上面的初阶问题,只是去判断链表中是否有环,那如果要求我们求出带环链表的入环第一个节点呢?又如何解决,这是环形链表的进阶问题,比如:

返回指向2的节点指针
返回指向1的结点指针

解决方法

方法一:

        利用相交链表求出环的起始节点。

思路过程:

        由初阶问题,可以得出快慢指针相遇的节点,设为meet,meet的下一节点设为new_head,将meet节点的next指针置为空(如图1),此时环就从meet节点处断掉了,那么求环的起始节点的问题就是求原先链表的表头head与new_head的两条链表的相交节点(如图2)。

--->

代码:

struct ListNode {
    int val;
    struct ListNode *next;
};

typedef struct ListNode Lnode;

//求两个链表的相交节点
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if((!headA)||(!headB))
    {
        return NULL;
    }
    int lena=0;
    int lenb=0;
    int i=0;
    Lnode* pa=headA;
    Lnode* pb=headB;
    while(pa)
    {
        lena++;
        pa=pa->next;
    }
    while(pb)
    {
        lenb++;
        pb=pb->next;
    }
    pa=headA;
    pb=headB;
    int mid=0;
    if(lena>lenb)
    {
        mid=lena-lenb;
        for(i=0;i<mid;i++)
        {
            pa=pa->next;
        }
    }
    else
    {
        mid=lenb-lena;
        for(i=0;i<mid;i++)
        {
            pb=pb->next;
        }
    }
    while(pa&&pb)
    {
        if(pa==pb)
        {
            return pa;
        }
        pa=pa->next;
        pb=pb->next;
    }
    return NULL;
}

//求环的起始节点
struct ListNode *detectCycle(struct ListNode *head) {
    if(!head)
    {
        return NULL;
    }
    Lnode* slow=head;
    Lnode* fast=head;
    while(fast)
    {
        slow=slow->next;

        fast=fast->next;
        if(!fast)
        {
            break;
        }
        fast=fast->next;

        if(slow==fast)
        {
            //有环
            Lnode* new_head=slow->next;
            slow->next=NULL;
            return getIntersectionNode(head,new_head);  //调用函数
        }
    }
    return NULL;
}

方法二:

        根据快指针的步数总是慢指针步数的二倍建立方程得出一定的关系。

思路过程:

        将head与环的起始节点的距离设为L,环的起始节点与快慢节点相遇点的距离设为X(如图),我们知道,当慢指针进入环中之后,在进行第二圈之前,快指针一定会追上它(因为快指针的步数总是慢指针步数的两倍,当慢指针开始第二圈时,说明慢指针已经走了完整的一圈,而快指针则是走了完整的两圈,这个过程中它们不可能没有相遇,况且快指针先进的环,所以相遇点一定是慢指针进行第一圈,快指针进行诺干圈的时候出现的),

所以慢指针的总步数为L+X,快指针的总步数为L+N*C+X(C表环的周长,N表圈数),又因为快指针的步数总是慢指针步数的二倍,有等式(L+X)*2=L+N*C+X,化简得L=N*C-X=(N-1)*C+C-X,意思是链表头到环起始节点得距离等于环周长的某倍加上C-X。

        因此,在得到相遇点之后,设一个节点指针在meet处,一个节点指针在链表头节点处,同时走,都每次走一步,最后一定会在环起始节点处相遇。

代码:

struct ListNode {
    int val;
    struct ListNode *next;
};

typedef struct ListNode Lnode;
struct ListNode *detectCycle(struct ListNode *head) {
    if(!head)
    {
        return NULL;
    }
    Lnode* slow=head;
    Lnode* fast=head;
    while(fast)
    {
        slow=slow->next;

        fast=fast->next;
        if(!fast)
        {
            break;
        }
        fast=fast->next;

        if(slow==fast)
        {
            //有环
            //让fast从相遇点走,slow从头走,都是一步一步走,再次相遇点就是环的入口
            slow=head;
            while(fast!=slow)
            {
                slow=slow->next;
                fast=fast->next;
            }
            return slow;
        }
    }
    return NULL;//无环
}

        以上就是环形链表问题的解释了,在力扣中都有对应的oj题可以做,听懂的可以去尝试一下,有不懂或者有疑问的小伙伴可以私我或者评论区,记得三连支持哦!

141. 环形链表 - 力扣(LeetCode)icon-default.png?t=N4N7https://leetcode.cn/problems/linked-list-cycle/142. 环形链表 II - 力扣(LeetCode)icon-default.png?t=N4N7https://leetcode.cn/problems/linked-list-cycle-ii/

猜你喜欢

转载自blog.csdn.net/phangx/article/details/130866380
今日推荐