此篇博文的oj题都来源于leetcode,链接如下:
环形链表1: link
环形链表2: link
建议大家可以事先做一下,这样可以你可以带着问题来阅读此片博文,掌握的也比较扎实,当然如果你
环形链表1
这个题让我们判断链表是否带环。
这里我们可以使用快慢指针,slow和fast,fast指针一次走两步,slow指针一次走一步,如果这是一个带环的链表,那么这两个指针一定会相遇。
代码如下
bool hasCycle(struct ListNode *head)
{
struct ListNode *slow = head, *fast = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return true;
}
}
return false;
}
我这么直接说出来,大家可能会不太理解,为什么这两个指针一定会相遇,请看下面的详解
我们假设下图为我们所要证明的带环链表
当我们的快指针fas移动到入环点时,slow则走到入环前距离的一半
当slow走到入环点的时候,fast大概移动到如下图环的位置
现在这个时候fast与slow都进入到了这个环中,进入了追击的过程,假如我们这里设,fast和slow的距离为n,那么没追击一次时,slow走一步,fast走两步,每追击一次它们两个之间的距离就会减1
所以它们一定会相遇
我么们这里拓展一个问题,如果让fast指针一次移动3步,或者一次移动4步可以吗?
我这里先给出正确答案,可能会相遇,也可能不会相遇。
我们这里就拿一次移动3步为例子进行解释,当slow入环时,那么就进入了追击的阶段,假如fast与slow之间的距离为n,每次追击时fast走三步,slow走一步,那么就相当于在每次追击时,它们之间的距离会减少2。
当n为偶数时
我们可以看见fast和slow是可以相遇的
假设当n为奇数时
通过上面个的动态图我们可以看见fast与slow会错过,但是这还没有结束,这是fast与slow之间的距离时-1,但是实际距离时C-1,这里的C为这个环的长度。
如果C-1还是为奇数的话则fast与slow就永远也不会相遇,如果为偶数则fast与slow就一定会相遇。
到这里第一个链表带环问题到这里就结束了
环形链表2
这个问题就属于是上一个问题的进阶了,他让你判断链表是否带环,如果带环则让你返回入环点的位置,否则返回空指针
这个问题我有两种方法来做,第一种方法逻辑性比较强,但是代码相对比较简单,第二种方法比较容易理解,但是代码相对比较复杂
第一种方法
还是和上一个题一样,我们先确定该链表是否为环形链表
然后fast指针从这个相遇点开始,slow是指针从链表的head位置开始,它们两每次走一步开始遍历链表,它们一定会在入环的的位置相遇。
大家可能会不太理解,请看下面的详解
我们设入环前链表的长度为L,环的长度为C,设slow在环内走的距离为X
我们让在证明该链表是否带环时slow走的距离与fast走的距离组建方程式,因为fast一次走两步,slow一次走一步,所以fast走过的距离为slow的两倍,所以得到2(L+X)= L+X+(N*C),这里的N代表fast在环内走过的圈数。
我们化简可以得到L= (N*C) - X,我们为了进一步方便大家理解,我们可以化简成L=(N-1)*C+C-X
如果N为1的话那么L=C-X
所以只要让fast在相遇点以每次一步的速度开始遍历链表,slow在head位置以每次一步的速度开始遍历链表,它们一定会在入环点的位置相遇。
代码如下
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow = head, *fast = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
slow = head;
while (slow && fast)
{
if (fast == slow)
{
return fast;
}
slow = slow->next;
fast = fast->next;
}
}
}
return NULL;
}
第二种方法
第二种方法也是使用和第一种方法相同的方法确定链表是否带环
不同的时在确认完后要将相遇点记录下来,并且用fast记录相遇点的下一个节点,燃后将相遇点与下一个节点的连接打断,如下图
这样我们就可以的到两个有共同交点的链表,fast从这个节点开始遍历,slow从head这个位置开始遍历,找它们两个的第一个交点,这个交点就是入环点的位置。
现在的问题是怎么找这个交点,我们首先要知道这两个链表的长短,然后让长的链表先走,走到与短链表一样长的时候,短链表也开始遍历,这样我们就可以找到它们的第一个交点。
代码如下
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow = head, *fast = head;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
struct ListNode *meet =fast, *rhead = meet->next;
fast = meet->next;
meet->next = NULL;
slow = head;
int len1 = 0, len2 = 0, len = 0;
while (slow)
{
slow = slow->next;
len1++;
}
while (fast)
{
fast = fast->next;
len2++;
}
len = abs(len1 - len2); //得到两个链表长短的差值
struct ListNode *lgList = len1 > len2 ? head : rhead;
struct ListNode *stList = len1 < len2 ? head : rhead;
while (len--)
{
lgList = lgList->next;
}
while (lgList && stList)
{
if (lgList == stList)
{
return lgList;
}
lgList = lgList->next;
stList = stList->next;
}
}
}
return NULL;
}
这篇文章到此结束,感谢观看!