数据结构:链表带环问题的求解

在这里插入图片描述

个人主页 :个人主页
个人专栏 :《数据结构》《C语言》


一、确认链表带环

  • 思路
    我们定义两个变量,slow和fast,慢指针slow一次走一步,快指针fast一次走两步,则如果链表带环,slow和fast一定在环中相遇。思路很简单,但是为什么???

  • 解释1
    当slow刚进入环部分,fast与slow相距N个节点时。
    slow一次走一步,fast一次走两步。
    如下:
    在这里插入图片描述
    此时fast与slow的步长差是1。
    如果slow,fast先后移动一次,则fast与slow相距(N-1)个节点,若在进行一次,则fast与slow相距(N-2)个节点,循环往复则fast与slow终会相遇,也就是相距0个节点。
    如下:
    在这里插入图片描述
    所以slow与fast一定在环中相遇且不可能错过。

  • 延伸
    如果fast走n步(n > 2)?slow与fast还一定在环中相遇吗?

  • 解释2
    当slow刚进入环部分,fast与slow相距N个节点时。
    假设slow一次走一步,fast一次走3步。
    如下:
    在这里插入图片描述
    此时slow与fast的步长差是 2。

如果slow与fast先后移动一次,则slow与fast相距为(N-2),移动两次,相距为(N-4),循环往复,我们会发现当N是偶数时,slow与fast移动N/2次,相距为0,二者相遇。当N是奇数时,slow与fast移动N/2次,二者相距为1,slow与fast再次移动,则二则相距-1,也就是C(环的大小)-1。如果C-1是偶数,则slow与fast在移动(C-1)/2次,二者会相遇。如果C-1是奇数,则slow与fast再移动(C-1)/2次,二者还是相距-1,也就是说,二者会死循环,永远的错过。
如下:
在这里插入图片描述
也就是说,fast移动3步时,有可能会与slow永远错过不相遇。
那fast移动4步呢?
如下:
在这里插入图片描述
依次往后寻找,我们会发现只有当N时fast与slow步长差的整数倍时,slow与fast才会相遇

  • 代码实现
//141. 环形链表
bool hasCycle(struct ListNode* head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;

    if (head == NULL)
    {
        return false;
    }

    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;

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

    return false;

}

二、返回链表的头节点

1.公式法

情况如下:
在这里插入图片描述
也就是说,newhead与meet同时移动当二者相遇是,相遇的点就是链表圆环部分的头结点。

  • 代码如下:
142. 环形链表 II(返回环的头节点)
// 
// 公式法
// 到相遇的距离                         C是环的大小
// slow走的距离是L+X,fast走的距离是L+N*C+X
// 则2*slow == fast
//   2*(L + X) = L + N*C + X
//      L = N * C - X
//		L = (N - 1) * C + C - X
//		L = C - X
//链表除环的长度    相遇点到换头的距离  
//        
struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    if (head == NULL)
    {
        return NULL;
    }

    //外层循环找环
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if (slow == fast)
        {
            struct ListNode* meet = slow;
            struct ListNode* newhead = head;
            //找环头节点
            while (newhead != meet)
            {
                meet = meet->next;
                newhead = newhead->next;
            }

            return newhead;
        }
    }

    return NULL;
}

2.将问题转换成两个链表相交,求交点问题

  • 思路
    如果链表有环,也就是说fast与slow会相遇,那么我们将meet(相遇点)当成链表的尾节点,meet下一个节点当成新链表的头结点,让新的头节点与旧的头节点同时移动,当二者相遇是,相遇点就是链表环部分的头结点。
    如下:
    在这里插入图片描述

  • 代码如下


// 将找环头结点转化为两个链表相交问题
// 让slow与fast相遇的点做为两个链表的尾节点,meet的下一个节点作为其中一条链表的头结点
// 原链表的头结点是另一条链表的头结点
#include <stdlib.h>

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    if (head == NULL)
    {
        return NULL;
    }
	//外层循环找是否有环
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
		
        if (fast == slow)
        {
            struct ListNode* list2 = slow->next;
            struct ListNode* list1 = head;
			
            int len1 = 0;
            int len2 = 0;
			
            while (list2 != slow)
            {
                list2 = list2->next;
                len2++;
            }
			
            while (list1 != slow)
            {
                list1 = list1->next;
                len1++;
            }
			//区分长和短的链表
            int differ = abs(len1 - len2);
            struct ListNode* longlist = head;
            struct ListNode* shortlist = slow->next;
			
            if (len1 < len2)
            {
                longlist = slow->next;
                shortlist = head;
            }
			//长的链表先走
            while (differ--)
            {
                longlist = longlist->next;
            }
			//两个链表同时移动
            while (longlist != shortlist)
            {
                longlist = longlist->next;
                shortlist = shortlist->next;
            }

            return longlist;
        }
    }

    return NULL;
}

总结

以上就是我对于链表带环方面的总结。
在这里插入图片描述

感谢各位佬的观看!!!

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水月梦镜花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值