【数据结构】链表带环问题

【数据结构】链表带环问题


此篇博文的oj题都来源于leetcode,链接如下:
环形链表1: link
环形链表2: link
建议大家可以事先做一下,这样可以你可以带着问题来阅读此片博文,掌握的也比较扎实,当然如果你

环形链表1

在这里插入图片描述
这个题让我们判断链表是否带环。
这里我们可以使用快慢指针,slow和fast,fast指针一次走两步,slow指针一次走一步,如果这是一个带环的链表,那么这两个指针一定会相遇。
在这里插入图片描述
代码如下

bool hasCycle(struct ListNode *head)
 {
    struct ListNode *slow = head, *fast = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return true;
        }
    }
    return false;
}

我这么直接说出来,大家可能会不太理解,为什么这两个指针一定会相遇,请看下面的详解
我们假设下图为我们所要证明的带环链表
在这里插入图片描述
当我们的快指针fas移动到入环点时,slow则走到入环前距离的一半
在这里插入图片描述
当slow走到入环点的时候,fast大概移动到如下图环的位置
在这里插入图片描述
现在这个时候fast与slow都进入到了这个环中,进入了追击的过程,假如我们这里设,fast和slow的距离为n,那么没追击一次时,slow走一步,fast走两步,每追击一次它们两个之间的距离就会减1
在这里插入图片描述
所以它们一定会相遇
在这里插入图片描述
我么们这里拓展一个问题,如果让fast指针一次移动3步,或者一次移动4步可以吗?
我这里先给出正确答案,可能会相遇,也可能不会相遇。
我们这里就拿一次移动3步为例子进行解释,当slow入环时,那么就进入了追击的阶段,假如fast与slow之间的距离为n,每次追击时fast走三步,slow走一步,那么就相当于在每次追击时,它们之间的距离会减少2。

在这里插入图片描述
当n为偶数时
在这里插入图片描述
我们可以看见fast和slow是可以相遇的
假设当n为奇数时

在这里插入图片描述
通过上面个的动态图我们可以看见fast与slow会错过,但是这还没有结束,这是fast与slow之间的距离时-1,但是实际距离时C-1,这里的C为这个环的长度。
如果C-1还是为奇数的话则fast与slow就永远也不会相遇,如果为偶数则fast与slow就一定会相遇。

到这里第一个链表带环问题到这里就结束了

环形链表2

在这里插入图片描述
这个问题就属于是上一个问题的进阶了,他让你判断链表是否带环,如果带环则让你返回入环点的位置,否则返回空指针
这个问题我有两种方法来做,第一种方法逻辑性比较强,但是代码相对比较简单,第二种方法比较容易理解,但是代码相对比较复杂

第一种方法

还是和上一个题一样,我们先确定该链表是否为环形链表在这里插入图片描述
然后fast指针从这个相遇点开始,slow是指针从链表的head位置开始,它们两每次走一步开始遍历链表,它们一定会在入环的的位置相遇。
大家可能会不太理解,请看下面的详解
我们设入环前链表的长度为L,环的长度为C,设slow在环内走的距离为X

在这里插入图片描述
我们让在证明该链表是否带环时slow走的距离与fast走的距离组建方程式,因为fast一次走两步,slow一次走一步,所以fast走过的距离为slow的两倍,所以得到2(L+X)= L+X+(N*C),这里的N代表fast在环内走过的圈数。
我们化简可以得到L= (N*C) - X,我们为了进一步方便大家理解,我们可以化简成L=(N-1)*C+C-X
如果N为1的话那么L=C-X
所以只要让fast在相遇点以每次一步的速度开始遍历链表,slow在head位置以每次一步的速度开始遍历链表,它们一定会在入环点的位置相遇。

在这里插入图片描述
代码如下

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

第二种方法

第二种方法也是使用和第一种方法相同的方法确定链表是否带环
不同的时在确认完后要将相遇点记录下来,并且用fast记录相遇点的下一个节点,燃后将相遇点与下一个节点的连接打断,如下图
在这里插入图片描述
这样我们就可以的到两个有共同交点的链表,fast从这个节点开始遍历,slow从head这个位置开始遍历,找它们两个的第一个交点,这个交点就是入环点的位置。
现在的问题是怎么找这个交点,我们首先要知道这两个链表的长短,然后让长的链表先走,走到与短链表一样长的时候,短链表也开始遍历,这样我们就可以找到它们的第一个交点。
在这里插入图片描述
代码如下

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *slow = head, *fast = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            struct ListNode *meet =fast, *rhead = meet->next;
            fast = meet->next;
            meet->next = NULL;
            slow = head;
            int len1 = 0, len2 = 0, len = 0;
            while (slow)
            {
                slow = slow->next;
                len1++;
            }
            while (fast)
            {
                fast = fast->next;
                len2++;
            }
            len = abs(len1 - len2); //得到两个链表长短的差值
            struct ListNode *lgList = len1 > len2 ? head : rhead;
            struct ListNode *stList = len1 < len2 ? head : rhead;
            while (len--)
            {
                lgList = lgList->next;
            }
            while (lgList && stList)
            {
                if (lgList == stList)
                {
                    return lgList;
                }
                lgList = lgList->next;
                stList = stList->next;
            }
        }
    }    
    return NULL;
}

这篇文章到此结束,感谢观看!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在C语言中,单链表带头结点是指在链表的头部添加一个额外的结点,该结点不存储任何数据,仅用于标记链表的起始位置。引用\[3\]中的代码展示了一个带头结点的单链表的定义,其中node*表示指向一个结点的指针,linklist表示指向链表的指针。 在带头结点的单链表中,头结点的next指针指向链表的第一个实际存储数据的结点。这样做的好处是可以简化链表的操作,例如插入和删除结点时不需要特殊处理链表为空的情况。引用\[1\]和引用\[2\]中的代码展示了在带头结点的单链表中插入和删除结点的操作。 需要注意的是,在使用带头结点的单链表时,需要特别处理头结点的情况,例如在遍历链表时,通常从头结点的下一个结点开始遍历。此外,使用带头结点的单链表可以更方便地处理空链表的情况,因为头结点始终存在。 带头结点的单链表在实际应用中广泛使用,它可以更高效地进行插入、删除和查找等操作,并且可以更好地处理边界情况。 #### 引用[.reference_title] - *1* *2* *3* [带头节点的单链表(C语言版)](https://blog.csdn.net/zhu134/article/details/130632385)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

罗!伯!特!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值