环形链表I
对于本题,题目让我们判断链表中是否带环,如果带环,返回true,不带环返回false。
我们先来讨论不带环的情况。如果链表不带环,那么最后一个结点的next指针就为空指针。
如果带环的话,最后一个结点的next指针就会指向链表中的其他元素,直接遍历的话,就会造成死循环。
这时候我们就容易想到利用快慢指针的方法。
当slow到环的入口时,fast可能在slow的任何位置。
这时候我们只看这个环,可以把它联想为一个跑道,fast永远跑的比slow快,那么是不是fast在未来一定会追上slow呢?答案是肯定的。
最坏的情况:slow到环的入口时,fast刚好在slow前面一个结点,那么这时fast追上slow时,slow就需要走一整圈。花费的时间最长。
最好的情况:slow到环的入口时,fast刚好在slow后一个结点,那么这时fast追上slow只需要slow走一个结点。时间最短。
还有问题就是为什么slow走一步,fast必须要走两步,如果slow走一步,fast走三、四或者N步是否可以?
这里我们不进行深入探讨了,fast每次走多少步跟环的长度的奇偶性有关。
这样我们通过讨论,就能写出这个题的代码了。
bool hasCycle(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)
return slow;
}
return false;
}
环形链表II
解法一:数学思想
这题的判断条件为,如果不带环返回NULL,带环的话返回环的入口结点。
跟上一题一样,首先要判断是否带环,这里我就不说了。
接下来讨论如何求出环的入口结点。
我们对第一题的分析进行延申,还是利用快慢指针,我们假设从链表第一个结点到环的入口结点的距离为L,环的长度为C,从slow进入到环后两个指针第一次相遇的结点到环的入口距离为X。
这时,对于slow指针来说,它走过的路程为L+X;
对于fast指针来说,它走过的路程为L+N*C+X。(因为环的长度不确定,当环很小的时候,fast在slow进入环之前可能走了很多很多圈,当环很大的时候,可能一圈都没走完)
由于fast每次走两步,是slow速度的两倍,所以我们可以得到一个式子
2*(L+X) = L + N*C +X;
化简得
L = (N-1)*C + C - X;
仔细观察这个式子,它可以理解为一个指针每次走一步,从头结点开始,到环入口所走的路程等于一个指针每次走相同的步数,从环的入口走了N-1圈,又走了C-X的路程。而C-X 恰好等于从slow和fast相遇位置到环入口结点的距离。
从而,我们可以让一个指针从头开始走,另一个指针从fast和slow的相遇位置开始走,当两个指针相遇时,就是环的入口结点。
代码:
struct ListNode *detectCycle(struct ListNode *head) {
//判断环是否有环
struct ListNode* fast ,* slow;
fast = slow = head;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow == fast)
{
//第一次追上,标记meed
struct ListNode* meet = slow;
while(head != meet)
{
head=head->next;
meet=meet->next;
}
return head;
}
}
return NULL;
}
解法二:转换成相交链表
还是从这个图开始,我们让fast和slow相遇位置的结点设为空指针,让meet指针指向相遇结点的下一个位置,那么求环入口结点的位置就成为了求两个链表的第一个相交结点的问题了。
这两种解法所用的代码都是同一个,但是解题的思想有所不同。