如何判断一个单链表是否有环以及环入口

这是一个在我们学习数据结构的时候经常会遇到的问题,今天给大家带来这个问题的几种解法。

方法一

最容易想到的办法就是遍历单链表,如果单链表有环的话那么会进入死循环,但是我们不知道单链表的长度,所以如果单链表长度很长,我们一直向下遍历,也无法分辨出是单链表还没遍历完还是进入了死循环。

所以这种解法不靠谱。

方法二

我们可以在遍历单链表中的每个元素的时候,每遍历一个新的节点,就从头再开始遍历一次已经遍历过的节点,用新节点和之前遍历过的节点相比较,如果新节点之前遍历过的节点相同,就说明单链表有环。

单链表

比如说已存在的单链表:A->B->C->D->E->F->G->D,在遍历到第七个节点(D)的时候,再在从头节点开始遍历到第三个节点,因为第三个集结点(D)和第七个节点相同,所以单链表一定有环。

假设从单链表头节点到环入口节点的距离是D,单链表的环长是S。那么算法的时间复杂度是0+1+2+3+….+(D+S-1) = (D+S-1)*(D+S)/2,可以理解成O(N*N)。

算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1)。

所以这个算法的时间复杂度是O(N*N),空间复杂度是O(1)。

找环入口的话就很简单了,遍历到第一个判定单链表有环的节点(D)就是环的入口。

方法三

考虑到方法二每遍历一个新节点的时候都需要从头开始遍历遍历一遍节点,会导致时间复杂度很高,所以我们可以把已经遍历过的节点用一个HashSet存起来,然后每遍历一个新节点的时候再去和HashSet中的元素做匹配,如果HashSet中已存在该节点,那么单链表有环。

假设从单链表头节点到环入口节点的距离是D,单链表的环长是S。而每一次HashSet查找元素的时间复杂度是O(1),所以总体的时间复杂度是1*(D+S)=D+S,可以简单理解为O(N)。

而算法的空间复杂度还是D+S-1,可以简单地理解成O(N)。

这个算法的时间复杂度是O(N),空间复杂度是o(N).

找环入口和方法二一样,遍历到第一个判定单链表有环的节点(D)就是环的入口。

方法四

还有一种思路是创建两个变量slow和fast同时指向头节点,然后slow每次向后遍历一个节点,fast每次向后遍历两个节点,如果单链表没有环的话那么slow将永远追不上fast,而如果单链表有环的话slow就会追上fast。

单链表2

下面我们来模拟随着单链表的遍历slow和fast两个变量值的变化:

次数\变量slowfast
0AA
1BC
2CE
3DG
4EE
5FG
6GE
7DG
8EE

单链表3

可以看到遍历到第八次的时候,slow追上了fast,所以单链表有环。

假设从单链表头节点到环入口节点的距离是D,单链表的环长是S。那么循环会进行S*K次,K为正整数,可以简单理解为O(N)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。

这个算法的时间复杂地是O(N),空间复杂度O(1)。

那么如何找到环的入口呢?下面我们来进行一个小小的数学计算:设从单链表头节点到环入口节点的距离是D,环入口到相交点的距离是X,设slow和fast第一次相遇时fast走了n圈环,slow走的距离为len,那么fast走的距离是2*len,可以得出下面的两个等式:

len = D + X
2 * len = D + X + n * R

两个等式相减可以的到:len = n * R - X

通过这个等式我们可以再定义两个变量out和in,out指向单链表头节点,in指向之前slow和fast第一次相交的节点,out和in同时向后遍历,第一次相交的点就是环的入口。

单链表4

下面我们来模拟随着单链表的遍历out和in两个变量值的变化:

次数\变量outin
0AE
1BF
2CG
3DD

可以看到遍历到第三次的时候,out和in相遇在D节点,所以D节点时单链表的环入口。


今天关于单链表是否有环以及环入口的问题就分享到这里了,提供了四种解法,第一种解法不太友好也不太合理,第二中解法时间复杂度太高,第三种解法空间复杂度太高,第四种解法可能时目前的最优解法了,希望对大家有所帮助。


喜欢这篇文章的朋友,欢迎长按下图关注公众号lebronchen,第一时间收到更新内容。
扫码关注

可以使用快慢指针法来判断单链表是否并找出入口。以下是具体步骤和代码实现: ### 步骤 1. **判断链表是否**:定义两个指针,一个快指针 `fast` 和一个慢指针 `slow`,初始时都指向链表头。慢指针每次向前移动一步,快指针每次移动两步。在移动过程中,如果快指针和慢指针相遇,则说明链表有;如果快指针到达链表末尾(即 `fast` 或 `fast->next` 为 `NULL`),则说明链表无。 2. **找出入口**:当快慢指针相遇后,将其中一个指针(例如慢指针)重新指向链表头,然后让两个指针都以每次一步的速度移动,它们再次相遇的节点就是入口。 ### 代码实现(C++ 示例) ```cpp #include <iostream> // 定义链表节点结构 struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; // 判断链表是否并返回入口 ListNode* detectCycle(ListNode* head) { if (head == nullptr || head->next == nullptr) { return nullptr; // 链表为空或只有一个节点,无 } ListNode* slow = head; ListNode* fast = head; // 判断是否 bool hasCycle = false; while (fast != nullptr && fast->next != nullptr) { slow = slow->next; fast = fast->next->next; if (slow == fast) { hasCycle = true; break; } } // 如果有,找出入口 if (hasCycle) { slow = head; while (slow != fast) { slow = slow->next; fast = fast->next; } return slow; } return nullptr; // 无 } ``` ### 复杂度分析 - **时间复杂度**:$O(n)$,其中 $n$ 是链表的节点数。判断是否的过程中,快慢指针最多遍历链表一次;找出入口的过程中,两个指针也最多遍历链表一次。 - **空间复杂度**:$O(1)$,只使用了常数级的额外空间。
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值