发布于 

环形链表

两道环形链表OJ题

环形链表相关问题

环形链表I


此问题的总结来源于两道环形链表的OJ题:环形链表I环形链表II

先来看看环形链表I ,题目的是这样的:

按照题目的表述,我们知道如果一个链表不含环,那我通过遍历链表总能找到尾结点。但如果含环,那遍历这个链表就是死循环。我看到这题,可以说是毫无思路,因为我除了遍历也想不到其他的法子。

我了解到“快慢指针”可以很完美的解决这道题。它是这么想的:

定义两个指针:slowslowfastfast,让slowslow每次后移一个节点,fastfast每次后移两个结点,如果这个单链表中存在换,当fastfastslowslow都进环以后,fastfast一定可以追上slowslow。抽象成一个数学问题就是,一个跑的慢的人和一个跑的快的人,从不同起点出发,在环形跑道内跑步,在以后的某一个时刻,跑的快的人一定可以追上跑的慢的人。这是一个很典型的“追击问题”。

​ 示意图

如果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;
}

做完这道题,不妨思考思考这几个问题:

  • fastfast一定可以追上slowslow吗?即fast=slowfast = slow;有没有可能它们永远不会相遇?
  • lowlow一次走1步,fastfast一次走3步,行不行?4步呢?5步呢?

简单想一想第一个问题,直观感觉上如果fastfast一次不走2步,可能fast快碰到slowslow的时候会直接跳过slowslow,那这样fastfast可能不会与slowslow相遇。下面我们需要推导证明这一结论。

证明: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的后一个位置。此时,fastslow的距离N=C1.根据上述讨论,只有C1为偶数时,它们才可能相遇. 综上所述:slow第一次进环时,它们之间的距离N为偶数或N为奇数且C1为偶数时,fastslow才会相遇。除此之外,fastslow都不会相遇。 \begin{aligned} 证明:\\ &设slow指针的速度为1,fast指针的速度为3.当slow进环以后它们之间的距离为N.\\ &假设它们能恰好相遇,经历的时间为t.环的长度为C\\ &则有:\\ &\qquad \qquad \qquad \qquad \qquad 1\times t + N = 3\times t \\ &解得:\\ &\qquad \qquad \qquad \qquad \qquad \qquad \quad N = 2\times 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都不会相遇。 \end{aligned}

我以fastfast一次走3步为例,简单讨论了slowslowfastfast会不会相遇的问题。当fastfast一次走44步时,推导过程类似。此时NN要满足NN为3的倍数,或者CC满足C1C-1C2C-2均为33的倍数时,才会相遇。

由此,总结出一般规律:

slow一次走1步,fast一次走n步时,当且仅当N(n1)的倍数或C1,C2,,C(n2)均为3的倍数时,才能保证fast一定可以与slow相遇。 \begin{aligned} &当slow一次走1步,fast一次走n步时,当且仅当N为(n-1)的倍数或C-1,C-2,\ldots,C-(n-2)\\ &均为3的倍数时,才能保证fast一定可以与slow相遇。 \end{aligned}

环形链表II


题目是这样表述的:

此题可以说是上一题的升级版,难度也有所增加。和上一题一样,我对此题也束手无策,只好从结果出发,总结总结此题。

首先最外层的肯定是先判断给的链表是否含环,这和上一题是一模一样的,难点就在于怎么找到环的入口,那就是最后一个结点的nextnext所指向的节点。针对此题,有一个结论:

slowslow进环以后,fastfast第一次与slowslow相遇时的节点到入口的距离等于链表第一个节点到环入口的距离。

简言之,就是:记slowslowfastfast第一次相遇时的节点为meetmeet,第一个节点为headhead,他们同时开始nextnext,那最终一定会在环入口处相遇。

下面我们来推导证明这一结论:

​ 示意图

证明:设第一个节点到环入口的距离为L,环长为C,第一次相遇时slow在环内走过的距离为x.分析可知:1slow在环内走不完一圈,fast就会追上slow.2fastslow相遇时,fast在环内走了nC+x的距离.(比较难想)由已知条件:fast走的比slow2倍可知,它们走过的距离也是2倍的关系.则可列方程:L+nC+x=2(L+x)解得:nC=L+x为了便于理解,可将上述解写为:(n1)C+Cx=L易知:Cx为第一次相遇时的位置到环入口处的距离,那么头结点到环入口的距离L就等于fastslow第一次相遇时的位置到环入口处的距离再加上环长的整数倍.这就意味着如果head指针和meet指针同时以每次移动1步的速度向后走,那么它们一定会在环的入口处相遇. \begin{aligned} 证明:\\& 设第一个节点到环入口的距离为L,环长为C,第一次相遇时slow在环内走过的距离为x.\\ & 分析可知:\\ & 1、slow在环内走不完一圈,fast就会追上slow.\\ & 2、fast与slow相遇时,fast在环内走了nC+x的距离.(比较难想) \\ & 由已知条件:fast走的比slow快2倍可知,它们走过的距离也是2倍的关系.\\ & 则可列方程:\\ & \qquad \qquad \qquad \qquad L+nC+x = 2(L+x)\\ &解得:\\ &\qquad \qquad \qquad \qquad \qquad \quad nC = L +x \\ &为了便于理解,可将上述解写为:\\ &\qquad \qquad \qquad \qquad (n-1)C+C-x = L\\ &易知:C-x为第一次相遇时的位置到环入口处的距离,那么头结点到环入口的距离L\\ &\qquad 就等于fast和slow第一次相遇时的位置到环入口处的距离再加上环长的整数倍.\\ &\qquad 这就意味着如果head指针和meet指针同时以每次移动1步的速度向后走,那么\\ &\qquad 它们一定会在环的入口处相遇. \end{aligned}

通过上面的分析,很容易就可以写出代码。此题的难点就在于发现这个结论,感觉回到了一个数学问题上。

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------------------------------------------------


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @Miracle 创建,使用 Stellar 作为主题。