环形链表
两道环形链表OJ题
环形链表相关问题
环形链表I
此问题的总结来源于两道环形链表的OJ题:环形链表I、环形链表II
先来看看环形链表I ,题目的是这样的:

按照题目的表述,我们知道如果一个链表不含环,那我通过遍历链表总能找到尾结点。但如果含环,那遍历这个链表就是死循环。我看到这题,可以说是毫无思路,因为我除了遍历也想不到其他的法子。
我了解到“快慢指针”可以很完美的解决这道题。它是这么想的:
定义两个指针:slow和fast,让slow每次后移一个节点,fast每次后移两个结点,如果这个单链表中存在换,当fast和slow都进环以后,fast一定可以追上slow。抽象成一个数学问题就是,一个跑的慢的人和一个跑的快的人,从不同起点出发,在环形跑道内跑步,在以后的某一个时刻,跑的快的人一定可以追上跑的慢的人。这是一个很典型的“追击问题”。

示意图
如果fast最后指向了空,那就说明这个链表中是不存在环的。 如果能想明白这个逻辑,那这道题的实现是非常简单的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| bool hasCycle(struct ListNode *head) { struct ListNode *fast = head; struct ListNode *slow = head; while(fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if(fast == slow) { return true; } } return false; }
|
做完这道题,不妨思考思考这几个问题:
- fast一定可以追上slow吗?即fast=slow;有没有可能它们永远不会相遇?
- low一次走1步,fast一次走3步,行不行?4步呢?5步呢?
简单想一想第一个问题,直观感觉上如果fast一次不走2步,可能fast快碰到slow的时候会直接跳过slow,那这样fast可能不会与slow相遇。下面我们需要推导证明这一结论。
证明:设slow指针的速度为1,fast指针的速度为3.当slow进环以后它们之间的距离为N.假设它们能恰好相遇,经历的时间为t.环的长度为C则有:1×t+N=3×t解得:N=2×t这表明在fast还没有超过slow之前,当且仅当它们之间的距离N为偶数时,fast第一次才会恰好等于slow.如果N不为偶数,那fast第一次超过slow时,一定是在slow的后一个位置。此时,fast和slow的距离N=C−1.根据上述讨论,只有C−1为偶数时,它们才可能相遇. 综上所述:当slow第一次进环时,它们之间的距离N为偶数或N为奇数且C−1为偶数时,fast与slow才会相遇。除此之外,fast与slow都不会相遇。我以fast一次走3步为例,简单讨论了slow与fast会不会相遇的问题。当fast一次走4步时,推导过程类似。此时N要满足N为3的倍数,或者C满足C−1、C−2均为3的倍数时,才会相遇。
由此,总结出一般规律:
当slow一次走1步,fast一次走n步时,当且仅当N为(n−1)的倍数或C−1,C−2,…,C−(n−2)均为3的倍数时,才能保证fast一定可以与slow相遇。
环形链表II
题目是这样表述的:

此题可以说是上一题的升级版,难度也有所增加。和上一题一样,我对此题也束手无策,只好从结果出发,总结总结此题。
首先最外层的肯定是先判断给的链表是否含环,这和上一题是一模一样的,难点就在于怎么找到环的入口,那就是最后一个结点的next所指向的节点。针对此题,有一个结论:
当slow进环以后,fast第一次与slow相遇时的节点到入口的距离等于链表第一个节点到环入口的距离。
简言之,就是:记slow与fast第一次相遇时的节点为meet,第一个节点为head,他们同时开始next,那最终一定会在环入口处相遇。
下面我们来推导证明这一结论:

示意图
证明:设第一个节点到环入口的距离为L,环长为C,第一次相遇时slow在环内走过的距离为x.分析可知:1、slow在环内走不完一圈,fast就会追上slow.2、fast与slow相遇时,fast在环内走了nC+x的距离.(比较难想)由已知条件:fast走的比slow快2倍可知,它们走过的距离也是2倍的关系.则可列方程:L+nC+x=2(L+x)解得:nC=L+x为了便于理解,可将上述解写为:(n−1)C+C−x=L易知:C−x为第一次相遇时的位置到环入口处的距离,那么头结点到环入口的距离L就等于fast和slow第一次相遇时的位置到环入口处的距离再加上环长的整数倍.这就意味着如果head指针和meet指针同时以每次移动1步的速度向后走,那么它们一定会在环的入口处相遇.通过上面的分析,很容易就可以写出代码。此题的难点就在于发现这个结论,感觉回到了一个数学问题上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct ListNode *detectCycle(struct ListNode *head) { struct ListNode *fast = head; struct ListNode *slow = head; while(fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if(fast == slow) { struct ListNode *meet = fast; struct ListNode *cur = head; while(cur != meet) { cur = cur->next; meet = meet->next; } return cur; } } return NULL; }
|
喔…终于写完了-.-
-----------------------------------------------end------------------------------------------------