链表带环问题(如何判断链表带环)

所谓带环就是一组链表种最后一个结点的next指针不指向NULL而是指向该链表的某个结点。

eg:

如何判断一个链表是否带环?

以目前阶段所学知识,快慢指针是唯一方案。

意思就算是先定义一个快指针,和一个慢指针,让快指针每次走两步,慢指针每次走一步。

如果当慢指针将要进入环中时,快指针已经在园内某个位置了。

在当快指针与慢指针相遇时,即可判断该链表带环。

如果不带环,则两则相遇是值next值都为NULL,也就是链表最后一个节点。

判断带环链表
看到这里简单做个题吧:141. 环形链表 - 力扣(LeetCode)

经过读题很简单就是判断是否为环形链表,结合我们上述分析如何判断带环,很容易写出。

对应代码:

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

引发出的问题(在有环的情况下)

1.为什么快慢指针一定会相遇?有没有会错过,永远也相遇不了?

2.快指针走两步可以,一次走三步,四步,五步,是否也可以?

证明:

快指针一次走两步,慢指针一次走一步

假设慢指针刚进环时,快慢指针距离为N,当慢指针走一步快指针走两步时,他们的距离会减小1,当他们距离减小到0时,他们就相遇了。因此可得快指针最终会追上慢指针的。

证明2:

当fast指针每次走三步时

当快慢指针距离为N时 他们的距离变化为

N, N-2, N-4, N-6...........

这时就会出现歧义, 当N为偶数时,我们知道最终会减为0也就是相遇。

但当N为奇数时我们会发现最后数值会减到-1(也就是比slow指针快了一步)。这时我们会进行第二次比较。

我们假设环的长度为C,这时两指针间距离为N=C-1

这时我们还需要判断N=C-1是否为奇,偶,若为偶数则能相遇,若为奇数则继续进行追击操作,最后的结果还是fast比slow指针多走一步,这时我们可以看到当C-1为奇数时该环陷入死循环(这点有点难理解可以亲手画一画理解一下)。

在这里先总结一下以上的分析:

1.如果N是偶数,第一轮就追上了。

2.如果N是奇数,第一轮追不上,进行第二轮追击

 a,如果C-1是偶数,则第二轮就追上  。

b如果C-1是奇数,则陷入死循环。

类比:

如果fast走4步,5步类似推到

进一步探索:

通过上面推导我们知道,快指针永远不会之追上慢指针的条件是:N(快慢距离)为奇数,C-1为奇(也就是C为偶数)时fast指针永远追不上slow指针。

但这种情况真的存在吗?让我们进一步探索。

我们假设没进入环的长度为L,slow指针进入环时与fast相差N,环长为C,当slow指针进入环时fast已经在环中转n圈。

当slow指针刚到环的时候有L+nC+C-N=3L(左边是快指针走的,右边是慢指针3倍)

整理一下得到 2L=(n+1)c-N

我们知道追不上的条件是N为奇数,c为偶数

把此条件带入反证:

偶数 =(n+1)*偶数-奇数

显然等式不成立,所以不存在此条件,一定会追上。

如何找到刚进环结点:

先给出结论:在快反指针相遇的地方定义一个meet结点,在链表头部定义一个head结点,两结点相遇即使带环入口结点。

看到这估计你们会很好奇为什么是这样,那就跟着小编来证明一下吧:

我们假设没乳环部分为L,相遇结点距离入口结点为N,环长为c,快指针转了n圈

快指针走的路程:L+nc+N

慢指针走的路程:L+N

因为我们假设快指针走两步,慢指针走一步。所以有L+nc+N=2(L+N)

整理得到:L=nc-N   =》  L=(n-1)c+c-N;

从上述式子可以看出前面(n-1)c是转的圈数,这个不影响的,因为不管转几圈 +c-N也会跟head相遇。所以退出c-N=L

返回入口结点:

. - 力扣(LeetCode)

首先我们要找到相遇结点—>然后让该结点与head结点同时走,相遇点即是入口结点

有关代码

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

通过案例:

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值