关于环形链表
环形链表如下图
它是一个单链表,但是尾结点->next不指向空及其它本身,而是指向链表中的其他结点,形成一个环。
OK废话不多说,进入正题!
怎么判断链表是否为循环链表!
分享一下我的学习经历
个人经历分享
我第一时间想到的是
创建一个足够大的结点指针数组,
将结点指针一个一个指向结点,
并且每加入一个结点之前判断一轮,该结点是否被数组中的结点指针指向过,
若有,则为环形链表。
代码如下:
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *a[100000];//可以称之为in数组,判断结点是否in这个数组中
int n=0;//每存储一个元素n++,记录a[]中的元素个数
struct ListNode* slow = head;//该指针用于一步一步遍历链表
while(slow)
{
a[n]=slow;
slow=slow->next;
n++;
for(int i =0;i<n;i++)
{
int count=0;
if(a[i]==slow)//若该结点出现在数组中
//则为环形链表,且此时slow为环形链表中环的起始点
{
return slow;
}
}
}
return NULL;//若循环正常退出,则不存在环
}
这是一个时间复杂度高,且空间复杂度高的做法,很暴力。
接下来介绍快慢指针
快慢指针
如图就是快慢指针的运行方式
fast 的步长为 2
slow 的步长为 1
循环跳出条件为 slow 追上 fast
以及 fast 为 NULL 或 fast->next 为 NULL
若是 slow 追上,则存在环
若是 fast 为 NULL 或 fast->next 为 NULL 跳出,则不存在环
求环起始点
例子:
若存在环则返回环的起始结点
若不存在环则返回 NULL
暴力求法
代码如下:
就是利用指针数组存每一个结点,
若遍历指针再次遇到该数组内的结点,
则跳出循环,输出遍历指针所指向的结点
若无,则正常跳出循环,输出 NULL
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *a[100000];
int n=0;
struct ListNode*slow= head;
while(slow)
{
a[n]=slow;
slow=slow->next;
n++;
for(int i =0;i<n;i++)
{
int count=0;
if(a[i]==slow)
{
return slow;
}
}
}
return NULL;
}
快慢指针
这里利用了一个数学关系
(head到环起始点,和快慢指针相遇点到环起始点的关系)
以该图为例
即 h 到 r 与 w 到 r 的关系
接下来是比较繁琐的讲解,请家人们耐心看,最好能动手写,体会含义
设:链表起始点为 h,环起始点为 r,链表总长为 L,w 为快慢指针相遇点
h 到 r 的距离为 a,r 到 w 的距离为 b(绿色部分)
环的长度为 k(红色加绿色的部分)
因为 快指针 是 慢指针 速度的 两倍
可以得到以下数学关系:
L
=
a
+
k
L = a + k
L=a+k
S l o w = a + b Slow = a + b Slow=a+b
F a s t = a + b + n k = 2 S l o w Fast = a + b + nk = 2Slow Fast=a+b+nk=2Slow
OK经过咱们最强大脑的演算得到:
a
+
b
=
n
k
=
(
n
−
1
)
k
+
k
a + b = nk = (n - 1)k + k
a+b=nk=(n−1)k+k
又
L
=
a
+
k
L = a + k
L=a+k
所以
a
+
b
=
(
n
−
1
)
k
+
L
−
a
a + b = (n - 1)k + L - a
a+b=(n−1)k+L−a
a = ( n − 1 ) k + ( L − a − b ) a = (n - 1)k + (L - a - b) a=(n−1)k+(L−a−b)
这里我们可以发现(n - 1)k实际上可以去掉,细品一下
得到
a
=
L
−
a
−
b
(红色部分)
a = L - a - b(红色部分)
a=L−a−b(红色部分)
此时就可以来解释为什么相遇后要令
**fast **步长改为1,slow 回到 head
此时 slow到 r 点,与 fast 到 r 点距离一致(fast 是在 w 点)
故继续走下去必定相遇,且在起始点 r
附上代码:
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast&&(fast->next))
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
{
slow=head;
while(slow!=fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
}
return NULL;
}
关于求环起始点,我认为用数学关系可以很一目了然的看出来,但是仔细想其中的道理我还是一知半解的样子,于是便用数学关系来解释这个地方。
题目来源:力扣网
参考资料:关于快慢指针寻找环入口证明