142. 环形链表 II
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *Slow = head;
ListNode *Fast = head;
while (Fast && Fast->next) {
Slow = Slow->next;
Fast = Fast->next->next;
if (Slow == Fast) {
ListNode *Head = head;
while (Head != Slow) {
Head = Head->next;
Slow = Slow->next;
}
return Head;
}
}
return nullptr;
}
};
能想到快慢指针很好,但是这个时候真的需要用数学来解决一下问题了。
$$ a=c+(n−1)(b+c) $$两边对环的周长 \(b+c\) 取模,事实上就可以得到
$$ a \equiv c\ \mathrm{mod} (b+c) $$
也就是说,在两个指针第一次相遇之后,我们就可以让 Slow 继续走完余下路程的同时,新指针Head 也走 \(c\) 步,由于 \(c\) 与 \(a\) 同余,并且此处是 Slow 指针而不是 Fast 指针在走——此处为重点,这意味着 \((n-1)(b+c)\) 这一项可以直接被消去——所以我们就可以得到第一个入环的结点了。
二刷
我的妈啊,这个思路是有的,但是细节上面的处理一定要注意。下面这种写法是不行的。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *Slow = head;
ListNode *Fast = head;
while (true) {
if (!Fast || !Fast->next)
return {};
// XXX
if (Fast == Slow) {
ListNode *Finder = head;
}
Slow = Slow->next;
Fast = Fast->next->next;
}
while (Finder != Slow) {
Slow = Slow->next;
Finder = Finder->next;
}
return Finder;
}
};
看着很整齐,是不是?但其实你别忘了你的 Slow 和 Fast 一开始就都被赋值成 head 了,所以那句 Fast == Slow 是永远为真的。有点搞。
所以这道题的正确方法应该是:先让 Slow 和 Fast 往前走,然后再考虑两个等于不等于的问题。但是由于我们要考虑 Fast 指针的可用性,就需要多写一些 guards 了。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *Slow = head;
ListNode *Fast = head;
while (Fast) {
// At the beginning, if Fast != nullptr, Slow must be so too.
Slow = Slow->next;
// However, we still need to validate Fast->next. This won't make our
// `Fast += 2; Slow += 1;` strategy fail, since if Fast->next is not
// available here, it means that we don't have cycles in this list.
if (!Fast->next)
return {};
Fast = Fast->next;
// In order to discriminate between exit when Fast->next unavailable and
// exit when Fast == Slow, we can only embed the another while loop here.
if (Fast == Slow) {
ListNode *Finder = head;
while (Finder != Slow) {
Slow = Slow->next;
Finder = Finder->next;
}
return Finder;
}
}
return {};
}
};
从这里你可以看出来,这个题目或许真的只能这么写……各种乱七八糟的 guards 和套叠,但只有这么写,这种方法才能奏效。只能说的确有那么点黑魔法的意思在里面。
虽然但是,其实哈希集合也未必不好用……
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> Visited;
while (head) {
if (!Visited.contains(head))
Visited.insert(head);
else
return head;
head = head->next;
}
return {};
}
};
三刷
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *Fast = head, *Slow = head;
while (Fast && Fast->next) {
Slow = Slow->next;
Fast = Fast->next->next;
if (Fast == Slow)
break;
}
if (!Fast || !Fast->next)
return nullptr;
Slow = head;
while (Fast) {
if (Fast == Slow)
break;
Fast = Fast->next;
Slow = Slow->next;
}
return Slow;
}
};
其实这个到底是不是比二刷的方法更好理解一点我也不太清楚,不过倒是确实更适合死记硬背了。