文章目录
引言
🌈
上一篇 【神秘海域】数据结构与算法
内容是 单链表及其接口
而本篇内容是对单链表的一个 非常重要
的补充: 带环单链表
。它,是大厂面试时可能会提问的内容,非常的重要!
本篇就是要结合题目来 详细分析一下 单链表的带环问题
带环单链表之前 : 快慢指针
🌈
在详细分析 带环单链表 之前,先分析两道题来了解一个非常重要的算法思路:快慢指针
题1:单链表的中间结点
🌈
原题描述是这样的:
示例 2
:
原题链接: Leetcode - 876. 链表的中间结点
这一题的解法,就需要使用到 快慢指针
的思路
那么什么是 快慢指针
?即,使用两个 移动速度不同
的指针在 数组
或 链表
等 序列结构上移动。
代码实现:
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
}
return pslow;
}
题2:链表中倒数最后k个结点
🌈
此题描述是这样的:
示例 2
:
原题链接:Nowcoder - JZ22 链表中倒数最后k个结点
本题的思路也是使用快慢指针,但是与上一题不同的是,本题是先走为快指针 与 后走为慢指针
代码实现:
struct ListNode* FindKthToTail(struct ListNode* pHead, int k )
{
struct ListNode* pfast = pHead;
struct ListNode* pslow = pHead;
while(k--)
{
if(pfast)
{
pfast = pfast->next;
}
else
{// 快指针指向空,即链表长度不到 k,直接返回 NULL
return NULL;
}
}
while(pfast)
{
pfast = pfast->next;
pslow = pslow->next;
}
return pslow;
}
在分析带环链表之前,需要 需要了解一下 快慢指针
,因为 带环链表的分析
是根据 快慢指针
分析的.
带环链表分析
🌈
分析 带环链表
,先 由一道题来引入:
题:环形链表
🌈
此题描述:
示例 2
:
示例 3
:
原题链接:Leetcode - 141. 环形链表
本题的思路也非常简单:
代码实现:
bool hasCycle(struct ListNode *head)
{
if(head == NULL)
return false;
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
if(pfast == pslow)
return true;
}
return false;
}
OK,带环链表的题做出来了
但是并没有结束 如果只是这样 怎么会有大厂提问呢?
带环链表的问题
🌈
在 链表带环
的基础上,还会延伸出几个问题:
- 快指针一次走两步,慢指针一次走一步,两指针一定会相遇吗?为什么?
- 如果 快指针一次走两步呢?三步呢?四步呢?为什么?
- 怎么找到带环链表的
入环节点
?
这才是 带环链表
真正需要知道的东西~
⭐带环链表深入分析⭐ *
🌈
问题1
🌈
快指针一次走两步,慢指针一次走一步,两指针一定会相遇吗?为什么?
来详细分析一下:
画图抽象图来分析,一个带环链表,抽象的形式可以看作:
快慢指针 同时
从首节点开始走,快指针走得快,慢指针走得慢
所以慢指针入环时,快指针早就已经入环了
此时的情况可能是(设一下,只是假设)
:
两个指针都入环之后,快指针开始在环内追逐慢指针:
因为 当这样的两个指针都入环之后,两个指针之间的距离变化就变为了 每走一步减一
所以,必定会相遇
问题2
🌈
如果 快指针一次走三步呢?四步呢?为什么?
快指针一次走多步,就需要看情况来分析了
快指针一次走三步:
上边我们分析了 快指针一次走两步
时的相遇情况:当两个指针都入环之后,其之间的距离是以 每次缩小 1
变化的
那么如果 快指针一次走三步
,那么 两个指针都入环之后,其之间的距离就是 以 每次缩小 2
变化的
每次缩小 2
,会造成什么情况呢?
快指针一次走四步:
当快指针 一次走四步
的时候,按照 一次走三步
的思路进行分析
X
为3
的倍数,可以相遇X
不为3
的倍数,且C-1
或C-2
也不为3
的倍数,就永远无法相遇C-1
和C-2
,需要更详细的分析
也就是说,快指针 一次走多步
能不能与慢指针相遇是 不确定的
。
实际的情况,与 环的长度
和 入环前链表的长度
都有关系,需要 具体情况具体分析
问题3
🌈
怎么找到带环链表的 入环节点
?
能够找到入环节点的一个前提是:快指针已经与慢指针相遇
。
详细分析一下:
首先还是画图假设一下:
参考图来看,慢指针 从开始
到 与快指针相遇
,走过的距离就是 :L + X
那么 快指针 走过的距离就是 : 2 * (L + X)
快指针走过的距离还可以怎么表示呢?
快指针走过的距离 还可以这样表示:L + X + N * C
(N表示走过的圈数)
所以,快指针 从开始
到 与慢指针相遇
走过的距离,就可以写成一个等式:
2 * (L + X) = L + X + N*C
化简一下就是: L + X = N * C
这个式子有什么用呢?
其实,这个等式说明:
如果,有两个指针同时以一次一步的速度,一个从 链表的首节点
开始,另一个从 快慢指针相遇点
开始,两个指针会在环的入口节点相遇。
为什么呢?
L + X = N * C
可以写为 --> L = N * C - X
一个指针从 链表首届点开始走,走过 L
长度 它的位置在入环节点
一个指针从 快慢指针相遇点 开始走, 走过 N * C
的长度,它的位置还在 快慢指针相遇点 ,但是如果走过 N * C - X
的长度,那么它的位置就也在 入环节点了,因为 入环节点到快慢指针相遇点的距离是 X
此时,入环节点就找到了。
题:寻找入环节点
🌈
分析完如何寻找入环节点,下面来尝试把这道题给做了:
题目描述:
示例 2
:
示例 3
:
原题链接:Leetcode - 142. 环形链表 II
代码实现:
// 大体思路与判断有环差不多
// 但是 有环时不能直接返回
struct ListNode *detectCycle(struct ListNode *head)
{
if(head == NULL)
return NULL;
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
if(pfast == pslow) // 有环
{
struct ListNode* phead = head;
while(phead != pslow) //使 两个指针 分别从 首节点和相遇点 一次一步 移动,直到相遇
{
phead = phead->next;
pslow = pslow->next;
}
return phead;
}
}
return NULL;
}
结语
🌈
本篇是对 单链表带环问题
的一个深入探索,单链表带环问题是 单链表中一个非常重要的应用 和 对单链表非常重要的理解。同时,他已经进入了大厂面试可能会考的范畴,重要的是对 单链表带环问题的深入分析
,而不是简单的判断是否有环
。
本篇文章到此结束
感谢阅读~
求评论、点赞、收藏~