《程序员面试金典》(第六版)习题:仅为记录一下以加强印象,不为商业用途,如有侵权请联系删除。以下源码和解释参考了书中源码以及这里:https://github.com/alexhagiopol/cracking-the-coding-interview。源码中节点以及功能函数定义参见这里:2.1 移除重复节点
template <typename T>
SinglyLinkedNode<T>* findLoopFirst(SinglyLinkedNode<T>* head)
{
SinglyLinkedNode<T>* F = head; // fast pointer
SinglyLinkedNode<T>* S = head; // slow pointer
//Find the first collision point
while (true)
{
// If there is no loop, return nullptr
if (F->getNext() == nullptr || F == nullptr)
{
return nullptr;
}
// Advance slow and fast pointers
S = S->getNext();
F = F->getNext()->getNext();
if (S == F) {
break;
}
}
// Reset slow pointer S to the head pointer
S = head;
// Find the start point of linked list's loop
while (true)
{
// Advance fast and slow pointers at same speed
S = S->getNext();
F = F->getNext();
if (S == F)
{
break;
}
}
return S;
}
这里举一个例子对算法加以说明,如图1所示。算法说明:设置两个快慢指针
f
a
s
t
fast
fast和
s
l
o
w
slow
slow,快指针每次移动两个节点,慢指针
s
l
o
w
slow
slow每次移动一个节点。如果链表中有环,则当慢指针移动
n
=
3
n=3
n=3步到链表中的环的起始点(黑点)时,此时快指针和慢指针都进入到环中,由于单向链表的原因,两个指针将一直在环中无限循环运行。当慢指针移动
n
=
3
n=3
n=3步到链表中的环的起始点(黑点)时,快指针肯定位于环中的某个节点,此时可以认为快指针顺时针滞后于慢指针。假设这里滞后
Z
Z
Z个节点(注意这里是
Z
Z
Z个节点不是快指针移动
Z
Z
Z步,即此时从快指针指向的节点开始迭代取
Z
Z
Z次
n
e
x
t
next
next指针可以取到黑色节点的指针),由于快指针每次移动两个节点,慢指针每次移动一个节点,实际看来每次操作快指针可以追赶慢指针一个节点。因此经过
Z
Z
Z次操作后快指针和慢指针将指向同一个节点。因此如果快慢指针发生碰撞,则说明链表中存在环。
当慢指针移动
n
=
3
n=3
n=3步到链表中的环的起始点,黑点,时,快指针移动了
6
=
2
∗
n
6=2*n
6=2∗n步到达红色节点,也即从链表中的环的起始点,黑点,开始移动算起的第
n
=
3
n=3
n=3个节点(此节点也可以说是从链表中的环的起始点,黑点,开始移动算起的第
n
=
11
n=11
n=11或第
n
=
19
n=19
n=19个节点,这是因为环的原因,当
n
n
n超过
L
o
o
p
S
i
z
e
LoopSize
LoopSize时就会出现这种情况。因此这里我们用
N
=
n
%
L
o
o
p
S
i
z
e
=
3
N=n \% LoopSize=3
N=n%LoopSize=3来代替此时快指针相对于环的起点移动的节点数。这里
L
o
o
p
S
i
z
e
=
8
LoopSize =8
LoopSize=8为链表中环的节点个数)这时快指针顺时针滞后于慢指针
L
o
o
p
S
i
z
e
−
N
=
5
LoopSize-N=5
LoopSize−N=5个节点。上面提到过经过
L
o
o
p
S
i
z
e
−
N
=
5
LoopSize-N=5
LoopSize−N=5次操作后快指针和慢指针将指向同一个节点,绿点,这时绿点距离环的起点,黑点,
L
o
o
p
S
i
z
e
−
(
L
o
o
p
S
i
z
e
−
N
)
=
N
=
3
LoopSize-(LoopSize-N)=N=3
LoopSize−(LoopSize−N)=N=3个节点,现在链表头节点距离环的起点,黑点,也是
N
=
n
%
L
o
o
p
S
i
z
e
=
3
N=n \% LoopSize=3
N=n%LoopSize=3个节点。此时将slow指针赋值为头节点的指针,
f
a
s
t
fast
fast指针还是指向绿点,同时
f
a
s
t
fast
fast指针和
s
l
o
w
slow
slow指针同时向下一个节点移动,每次移动一个节点,当两个指针指向的节点相同时,该节点即为环路的起始处。
该算法的空间复杂度为O(1)。该算法的时间复杂度为O(n),n为链表中节点的个数。