Leetcode 142.环形链表 II


题目描述:

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

进阶:你是否可以使用 O(1) 空间解决此题?

示例 1:

1
2
3
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

1
2
3
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

1
2
3
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

  • 链表中节点的数目范围在范围 $[0, 10^4]$ 内
  • $-10^5 <= Node.val <= 10^5$
  • pos 的值为 -1 或者链表中的一个有效索引

链接:

https://leetcode-cn.com/problems/linked-list-cycle-ii


题目分析

  这道题是 Leetcode 141.环形链表 的进阶,在判断链表是否有环的基础上还需要返回环的开始结点。我们在那道题中采用的是快慢指针的做法,这道题应该可以使用的方法,但是我们需要解决一个问题:怎么获得环开始的结点?我们知道,快指针的速度是慢指针的两倍,而它们是同时出发的,则快指针走的路程也是慢指针的两倍。我们分析它们走过的路程是什么样的。如下图所示。蓝色的点是它们的相遇点。
leetcode142.jpg

  则我们可以知道,快指针走过的路程应该是 $a+n(b+c)+b$,而慢指针走过的路应该是 $a+b$。而快指针走过的路程是慢指针的两倍,则我们可以得到下面的等式

  通过变换我们可以得到 $a=c+(n-1)(b+c)$,则我们可以发现,在快慢指针的相遇点出发,走 c 步就刚好到了环的入口,而链表头部到环入口的距离(也即 a),就是从相遇点到环入口加上 n-1 个环(也即 b+c)的距离。则我们可以在快慢指针相遇后,令一个新的指针从 head 出发,与慢指针同时前进,经过了 a 的单位时间后,他们的相遇点就是环的入口。
  需要注意的是,当时在做前一道题的时候,我们使用的是 while 循环,让快慢指针先走了一步,它们的出发点不是 head,因此他们的相遇点也不会是上面分析的那样。这道题我们使用了上一道题提到过的 do-while 循环,将出发点修改为 head

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(!head) return nullptr;
ListNode *fast = head;
ListNode *slow = head;
do{
if(fast == nullptr || fast->next == nullptr) return nullptr;
fast = fast->next->next;
slow = slow->next;
}while(fast != slow);
ListNode *newp = head;
while(newp != slow){
newp = newp->next;
slow = slow->next;
}
return newp;
}
};

  时间复杂度:$O(n)$,其中 $n$ 为链表的长度。我们知道快慢指针相遇所需要的时间是 $O(n)$,而相遇后慢指针和新指针走的路程为 a,也不会超过 $n$。因此复杂度仍然是 $O(n)$。
  空间复杂度:$O(1)$。我们只需要三个指针的空间。