一、确认链表带环
-
思路
我们定义两个变量,slow和fast,慢指针slow一次走一步,快指针fast一次走两步,则如果链表带环,slow和fast一定在环中相遇。思路很简单,但是为什么??? -
解释1
当slow刚进入环部分,fast与slow相距N个节点时。
slow一次走一步,fast一次走两步。
如下:
此时fast与slow的步长差是1。
如果slow,fast先后移动一次,则fast与slow相距(N-1)个节点,若在进行一次,则fast与slow相距(N-2)个节点,循环往复则fast与slow终会相遇,也就是相距0个节点。
如下:
所以slow与fast一定在环中相遇且不可能错过。 -
延伸
如果fast走n步(n > 2)?slow与fast还一定在环中相遇吗? -
解释2
当slow刚进入环部分,fast与slow相距N个节点时。
假设slow一次走一步,fast一次走3步。
如下:
此时slow与fast的步长差是 2。
如果slow与fast先后移动一次,则slow与fast相距为(N-2),移动两次,相距为(N-4),循环往复,我们会发现当N是偶数时,slow与fast移动N/2次,相距为0,二者相遇。当N是奇数时,slow与fast移动N/2次,二者相距为1,slow与fast再次移动,则二则相距-1,也就是C(环的大小)-1。如果C-1是偶数,则slow与fast在移动(C-1)/2次,二者会相遇。如果C-1是奇数,则slow与fast再移动(C-1)/2次,二者还是相距-1,也就是说,二者会死循环,永远的错过。
如下:
也就是说,fast移动3步时,有可能会与slow永远错过不相遇。
那fast移动4步呢?
如下:
依次往后寻找,我们会发现只有当N时fast与slow步长差的整数倍时,slow与fast才会相遇
- 代码实现
//141. 环形链表
bool hasCycle(struct ListNode* head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
if (head == NULL)
{
return false;
}
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return true;
}
}
return false;
}
二、返回链表的头节点
1.公式法
情况如下:
也就是说,newhead与meet同时移动当二者相遇是,相遇的点就是链表圆环部分的头结点。
- 代码如下:
142. 环形链表 II(返回环的头节点)
//
// 公式法
// 到相遇的距离 C是环的大小
// slow走的距离是L+X,fast走的距离是L+N*C+X
// 则2*slow == fast
// 2*(L + X) = L + N*C + X
// L = N * C - X
// L = (N - 1) * C + C - X
// L = C - X
//链表除环的长度 相遇点到换头的距离
//
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
if (head == NULL)
{
return NULL;
}
//外层循环找环
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
struct ListNode* meet = slow;
struct ListNode* newhead = head;
//找环头节点
while (newhead != meet)
{
meet = meet->next;
newhead = newhead->next;
}
return newhead;
}
}
return NULL;
}
2.将问题转换成两个链表相交,求交点问题
-
思路
如果链表有环,也就是说fast与slow会相遇,那么我们将meet(相遇点)当成链表的尾节点,meet下一个节点当成新链表的头结点,让新的头节点与旧的头节点同时移动,当二者相遇是,相遇点就是链表环部分的头结点。
如下:
-
代码如下
// 将找环头结点转化为两个链表相交问题
// 让slow与fast相遇的点做为两个链表的尾节点,meet的下一个节点作为其中一条链表的头结点
// 原链表的头结点是另一条链表的头结点
#include <stdlib.h>
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
if (head == NULL)
{
return NULL;
}
//外层循环找是否有环
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
struct ListNode* list2 = slow->next;
struct ListNode* list1 = head;
int len1 = 0;
int len2 = 0;
while (list2 != slow)
{
list2 = list2->next;
len2++;
}
while (list1 != slow)
{
list1 = list1->next;
len1++;
}
//区分长和短的链表
int differ = abs(len1 - len2);
struct ListNode* longlist = head;
struct ListNode* shortlist = slow->next;
if (len1 < len2)
{
longlist = slow->next;
shortlist = head;
}
//长的链表先走
while (differ--)
{
longlist = longlist->next;
}
//两个链表同时移动
while (longlist != shortlist)
{
longlist = longlist->next;
shortlist = shortlist->next;
}
return longlist;
}
}
return NULL;
}
总结
以上就是我对于链表带环方面的总结。
感谢各位佬的观看!!!