【发布时间】:2024-01-08 12:07:01
【问题描述】:
有没有办法使用不超过两个指针找出链接列表中循环的开始? 我不想访问每个节点并将其标记为已看到并报告已看到第一个节点。还有其他方法吗?
【问题讨论】:
标签: loops linked-list find singly-linked-list cycle-detection
有没有办法使用不超过两个指针找出链接列表中循环的开始? 我不想访问每个节点并将其标记为已看到并报告已看到第一个节点。还有其他方法吗?
【问题讨论】:
标签: loops linked-list find singly-linked-list cycle-detection
第一步: 照常进行,你会用找到循环,即 有两个指针,一个一步递增,另一个两步递增,如果它们在某个时间相遇,则有一个循环。
第 2 步: 将一个指针冻结在原来的位置,并逐步增加另一个指针,计算您所做的步骤,当它们再次相遇时,计数将为您提供循环的长度(此与计算循环链表中的元素个数相同)。
步骤 3: 将两个指针重置为链表的开头,将一个指针递增到循环时间的长度,然后启动第二个指针。一步递增两个指针,当它们再次相遇时,它将成为循环的开始(这与从链接列表末尾找到第 nth 元素相同)。
【讨论】:
void loopstartpoint(Node head){
Node slow = head.next;;
Node fast = head.next.next;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast){
System.out.println("Detected loop ");
break;
}
}
slow=head;
while(slow!=fast){
slow= slow.next;
fast = fast.next;
}
System.out.println("Starting position of loop is "+slow.data);
}
【讨论】:
【讨论】:
请参阅this链接以获得全面的答案。
【讨论】:
首先我们尝试找出列表中是否有任何循环。如果循环存在,那么我们尝试找出循环的起点。为此,我们使用两个指针,即 slowPtr 和 fastPtr。在第一次检测(检查循环)中,fastPtr 一次移动两步,而 slowPtr 一次向前移动一步。
slowPtr 1 2 3 4 5 6 7 fastPtr 1 3 5 7 9 5 7
很明显,如果列表中有任何循环,那么它们将在点(上图中的点 7)相遇,因为 fastPtr 指针的运行速度比另一个快两倍。
现在,我们来解决寻找循环起点的第二个问题。
假设,他们在第 7 点相遇(如上图所示)。然后,slowPtr 退出循环并位于列表的开头,即在点 1,但 fastPtr 仍然在汇合点(点 7)。现在我们比较两个指针的值,如果它们相同,则它是循环的起点,否则我们向前移动一步(这里 fastPtr 也是每次移动一步)并再次比较直到找到相同的点。
slowPtr 1 2 3 4 fastPtr 7 8 9 4
现在想到一个问题,这怎么可能。所以有很好的数学证明。
假设:
m => length from starting of list to starting of loop (i.e 1-2-3-4) l => length of loop (i.e. 4-5-6-7-8-9) k => length between starting of loop to meeting point (i.e. 4-5-6-7) Total distance traveled by slowPtr = m + p(l) +k where p => number of repetition of circle covered by slowPtr Total distance traveled by fastPtr = m + q(l) + k where q => number of repetition of circle covered by fastPtr Since, fastPtr running twice faster than slowPtr Hence, Total distance traveled by fastPtr = 2 X Total distance traveled by slowPtr i.e m + q(l) + k = 2 * ( m + p(l) +k ) or, m + k = q(l) - p(l) or, m + k = (q-p) l or, m = (q-p) l - k So, If slowPtr starts from beginning of list and travels "m" length then, it will reach to Point 4 (i.e. 1-2-3-4) and fastPtr start from Point 7 and travels " (q-p) l - k " length then, it will reach to Point 4 (i.e. 7-8-9-4), because "(q-p) l" is a complete circle length with " (q-p) " times.
【讨论】:
m+q(l)+k=2*(m+p(l)+k) => m+k=q(l)-2*p(l)
这是在链表中查找循环开始的代码:
public static void findStartOfLoop(Node n) {
Node fast, slow;
fast = slow = n;
do {
fast = fast.next.next;
slow = slow.next;
} while (fast != slow);
fast = n;
do {
fast = fast.next;
slow = slow.next;
}while (fast != slow);
System.out.println(" Start of Loop : " + fast.v);
}
【讨论】:
数学证明+解决方案
Let 'k' be the number of steps from HEADER to BEGINLOOP.
Let 'm' be the number of steps from HEADER to MEETPOINT.
Let 'n' be the number of steps in the loop.
Also, consider two pointers 'P' and 'Q'. Q having 2x speed than P.
简单案例:当 k
当指针“P”位于 BEGINLOOP 时(即它会经过“k”步),Q 将经过“2k”步。因此,实际上,当 P 进入循环时,Q 比 P 领先 '2k-k = k' 步,因此,Q 现在比 BEGINLOOP 落后 'n-k' 步。
当 P 从 BEGINLOOP 移动到 MEETPONT 时,它会经过 'm-k' 步。在那个时候,Q 会走 '2(m-k)' 步。但是,由于他们相遇,并且 Q 在 BEGINLOOP 后面开始了“n-k”步,所以,有效地, '2(m-k) - (n-k)' 应该等于 '(m-k)' 所以,
=> 2m - 2k - n + k = m - k
=> 2m - n = m
=> n = m
这意味着,P 和 Q 的交汇点等于循环中的步数(或多个,一般而言,请参见下面提到的情况)。现在,在 MEETPOINT,P 和 Q 都落后了 'n-(m-k)' 步,即落后了 'k' 步,正如我们看到的 n=m。 所以,如果我们再次从 HEADER 开始 P,从 MEETPOINT 开始 Q,但这次的速度等于 P,P 和 Q 现在将只在 BEGINLOOP 相遇。
一般情况:说,k = nX + Y,Y (因此,k%n = Y)
当指针“P”位于 BEGINLOOP 时(即它会经过“k”步),Q 将经过“2k”步。因此,实际上,当 P 进入循环时,Q 比 P 提前了 '2k-k = k' 步。但是,请注意“k”大于“n”,这意味着 Q 会进行多轮循环。因此,实际上,Q 现在比 BEGINLOOP 落后 'n-(k%n)' 步。
当 P 从 BEGINLOOP 移动到 MEETPOINT 时,它会经过“m-k”步。 (因此,实际上,MEETPOINT 现在将比 BEGINLOOP 提前 '(m-k)%n' 步。)在那个时候,Q 将行进 '2(m-k)' 步。但是,由于他们相遇了,并且 Q 在 BEGINLOOP 后面开始了 'n-(k%n)' 步,所以,实际上,Q 的新位置(即 '(2(mk) - (nk/%n))%n ' from BEGINLOOP) 应该等于 P 的新位置(即来自 BEGIN LOOP 的 '(mk)%n')。
所以,
=> (2(m - k) - (n - k%n))%n = (m - k)%n
=> (2(m - k) - (n - k%n))%n = m%n - k%n
=> (2(m - k) - (n - Y))%n = m%n - Y (as k%n = Y)
=> 2m%n - 2k%n - n%n + Y%n = m%n - Y
=> 2m%n - Y - 0 + Y = m%n - Y (Y%n = Y as Y < n)
=> m%n = 0
=> 'm' should be multiple of 'n'
【讨论】:
我找到的最佳答案在这里:
tianrunhe: find-loop-starting-point-in-a-circular-linked-list
p1 在 V 处移动,p2 在 2*V 处移动
当 2 个指针相遇时:跑的距离 = m+ n*L -d = 2*(m+ L -d)
=> 这意味着(这里没有数学证明)如果 p1 从 HEAD 开始 & p2 从 MEETING_POINT 开始并且他们以相同的速度移动,他们将遇到@START_LOOP
【讨论】:
按照您将用来查找循环的常用方法继续。 IE。有两个指针,一个单步递增,另一个分两步递增,如果它们在某个时间相遇,则有一个循环。
保持其中一个指针固定得到循环中的节点总数,比如 L。
现在使用循环中同一点的第二个指针(循环中断)遍历链表并计算剩余的节点数,例如 Y
循环在 ((X+Y)-L)\2 个节点之后开始。或者从第 (((X+Y)-L)\2+1) 个节点开始。
【讨论】:
按照您将用来查找循环的常用方法继续。 IE。有两个指针,一个一步递增,另一个两步递增,如果它们在某个时间相遇,则有一个循环。
保持其中一个指针固定得到循环中的节点总数,比如 L。
现在使用循环中同一点的第二个指针(循环中断)遍历链表并计算剩余的节点数,例如 Y
循环在 ((X+Y)-L)\2 个节点之后开始。或者从第 (((X+Y)-L)\2+1) 个节点开始。
【讨论】:
按照您将用来查找循环的常用方法进行操作。 IE。有两个指针,一个单步递增(slowPointer),另一个分两步递增(fastPointer),如果它们都在某个时间相遇,那就是一个循环。
您可能已经意识到,汇合点位于循环头部之前的 k 步。
其中 k 是列表中非循环部分的大小。
现在慢慢移动到循环的头部
在碰撞点保持快速
它们中的每一个都是从循环开始开始的 k 步(从列表的开始开始很慢,在循环头部之前的 k 步——绘制图片以获得清晰度)
现在以相同的速度移动它们 - 它们必须在循环开始时相遇
例如
slow=head
while (slow!=fast)
{
slow=slow.next;
fast=fast.next;
}
【讨论】:
好吧,我尝试了一种使用一个指针的方法...我在几个数据集中尝试了该方法...由于链表的每个节点的内存都是按递增顺序分配的,因此在遍历链表从链表的头开始,如果一个节点的地址变得大于它所指向的节点的地址,我们就可以确定有一个循环,以及循环的开始元素。
【讨论】:
有两种方法可以在链接列表中找到循环。 1.如果有循环,使用两个指针一个前进一步,另一个前进两步,在某些时候两个指针都得到相同的值并且永远不会达到空值。但是,如果没有循环指针在某一点达到 null 并且两个指针永远不会获得相同的值。但是在这种方法中,我们可以在链接列表中找到一个循环,但我们无法确定循环的确切开始位置。这也不是有效的方法。
【讨论】:
我听说过这个确切的问题是一个面试测验问题。
最优雅的解决方案是:
将两个指针都放在第一个元素上(称它们为 A 和 B)
然后继续循环::
如果你想真正找到有两个指针指向它的元素,那就更难了。除非你愿意多次重复链表,否则我会说不可能只使用两个指针。
使用更多内存的最有效方法是将指向元素的指针放入数组中,对其进行排序,然后查找重复。
【讨论】: