面试:删除链表中的循环 - Java
我在面试中被问到这个问题:“如何检测链表中的循环?”,我解决了这个问题,但面试官立即问我如何删除链表中的循环。我摸索着。
那么关于如何解决这个问题的任何指示可能是伪代码或方法定义?
我对 Java 很熟悉,所以我在 java 下标记了这个问题。
例如这个链表有一个循环
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
I was asked this question in interview: "How to detect the loop in linked list?", I solved this but immediately the interviewer asked me how do I remove the loop in a linked list. I fumbled.
So any pointers on how to solve this, may be pseudo code, or method definition?
I'm comfortable with Java so I have tagged this question under java.
For Instance this linked list has a loop
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这个问题有两个部分:
一旦知道循环开始的位置,就很容易识别列表中的最后一个元素,因为它是列表中紧随循环的开始,最终指向循环的开始。然后,将此元素的下一个指针/引用设置为 null 来纠正循环链接列表(不是循环链接列表,循环链接列表是最后一个元素指向第一个元素的位置 - 这将是一个循环列表的具体实例)。
Floyd 的循环检测算法,也称为龟兔算法,因为它涉及使用以不同速度移动的两个指针/参考是检测周期的一种方法。如果存在循环,则两个指针(例如
p1
和p2
)将在有限数量的步骤后最终指向同一个元素。有趣的是,可以证明它们相遇的元素到循环开始的距离与开始的距离相同(继续以相同的向前方向遍历列表)循环的位置是列表的头部。。也就是说,如果列表的线性部分有k
个元素,则两个指针将在长度为m
的循环内于点mk
处相遇从循环的开始或k
元素到循环的“结束”(当然,这是一个循环,因此它没有“结束” - 它只是再次“开始”)。这为我们提供了一种找到循环起点的方法:一旦检测到循环,让
p2
保持指向上述步骤的循环终止但重置的元素>p1
以便它指向列表的头部。现在,将每个指针一次移动一个元素。由于p2
在循环内部开始,因此它将继续循环。经过k
步(等于循环起点到列表头部的距离)后,p1
和p2
将再次相遇。这将为您提供循环开始的引用。现在可以轻松设置
p1
(或p2
)以指向开始循环的元素并遍历循环直到p1
结束up 指向起始元素。此时p1
正在引用“最后一个”元素列表,并且它的下一个指针可以设置为null
。下面是一些快速而肮脏的 Java 代码,假设有一个
Node
的链接列表,其中Node
具有next
引用。这可以优化,但它应该给你基本的想法:这个解释可能有助于解释第二部分背后的原因:
更多参考:
There are two parts to this problem:
Once you know where the loop starts, it's easy to identify the last element in the list since it's the element in the list following the start of the loop that ends up pointing back to the start of the loop. It is then trivial to set the next pointer/reference of this element to
null
to correct the cyclic link list (not circular linked list which is where the last elements points back to the first - this would be a specific instance of cyclic lists).Floyd's cycle detect algorithm, also called the tortoise and hare algorithm as it involves using two pointers/references that move at different speeds, is one way of detecting the cycle. If there is a cycle, the two pointers (say
p1
andp2
) will end up pointing to the same element after a finite number of steps. Interestingly, it can be proved that the element at which they meet will be the same distance to the start of the loop (continuing to traverse the list in the same, forward direction) as the start of the loop is to the head of the list. That is, if the linear part of the list hask
elements, the two pointers will meet inside the loop of lengthm
at a pointm-k
from the start of the loop ork
elements to the 'end' of the loop (of course, it's a loop so it has no 'end' - it's just the 'start' once again). And that gives us a way to find the start of the loop:Once a cycle has been detected, let
p2
remain pointing to the element where the loop for the step above terminated but resetp1
so that it's pointing back to the head of the list. Now, move each pointer one element at a time. Sincep2
began inside the loop, it will continue looping. Afterk
steps (equal to the distance of the start of the loop from the head of the list),p1
andp2
will meet again. This will give you a reference to the start of the loop.It is now easy to set
p1
(orp2
) to point to the element starting the loop and traverse the loop untilp1
ends up pointing back to the starting element. At this pointp1
is referencing the 'last' element list and it's next pointer can be set tonull
.Here's some quick and dirty Java code assuming a linked list of
Node
s where aNode
has anext
reference. This could be optimized but it should give you the basic idea:This explanation might help the why behind Part II:
More references:
解决方案 1 - 由职业杯和《破解编码面试》一书提供:
此解决方案的解释直接来自书本:
解决方案 2 上见面 - 由我提供:)
我希望这会有所帮助。
赫里斯托
Solution 1 - courtesy of Career Cup and "Cracking the Coding Interview" book:
The explanation for this solution is straight from the book:
Solution 2 - courtesy of me :)
I hope this helps.
Hristo
这个回答并不是为了争夺答案,而是为了进一步解释一下龟兔赛跑算法中两个节点的相遇。
两个节点最终都会进入循环。因为一个 (F) 移动得比另一个 (S) 快,所以 (F) 最终会绕 (S)。
如果循环的起点位于列表的头部,则 (F) 必须在列表的头部与 (S) 相遇。这只是因为 (F) 的速度是 (S) 的 2 倍;如果是 3X,则事实并非如此。这是正确的,因为当 (S) 完成半圈时,(F) 也完成一圈,因此当 (S) 完成第一圈时,(F) 已经完成两圈 - 并且与 (S) 一起回到循环起点.
如果循环的起点不在列表的头部,则当 (S) 进入循环时,(F) 已在循环中从 (k) 个节点开始。因为 (S) 的速度一次只有一个节点,所以它将在从循环开始起的 (k) 个节点处与 (F) 相遇 - 例如,在到达起点之前还需要 (k) 步,而不是在到达起点之后的 (k) 步开始。如果 (S) 的速度不是 1 并且 (F) 和 (S) 之间的速度比不是 2:1,则情况不成立。
3.1。这就是解释起来有点棘手的地方。我们可以同意 (F) 将继续重叠 (S) 直到它们最终相遇(参见上面的 1),但是为什么在从循环开始起的 (k) 个节点处呢?考虑以下等式,其中 M 是节点数或环路距离,k 是领先起点 (F);该方程将左侧给定时间 t 下 (F) 行驶的距离换算为右侧 (S) 行驶的距离:
d_F(t) = 2 * d_S(t) + k
因此,当 (S) 进入循环并在循环中行进了 0 距离时,(F) 只行进了 (k) 距离。当 d_S = M - k 时,d_F = 2M - k。因为我们还必须使用模数学,考虑到 M 代表循环中单圈的总距离,所以 (F) 和 (S) 在任何整个 M(无余数)处的位置为 0。因此,根据POSITION(或微分),这留下 k(或更确切地说,-k)。
最后,(S) 和 (F) 将在距离循环起点 (0 - k) 或 (k) 个节点处相遇。
根据上面的[3],as (k) 表示领先位置 (F),并且 as (F) 已经移动了从列表头部进入循环的距离 (S) 的 2 倍,(k)也表示距列表起点的距离,该距离代表循环的起点。
现在有点晚了,所以我希望我已经表达清楚了。否则请告诉我,我会尝试更新我的回复。
This response is not meant to compete for the answer, but rather to explain a little more on the meeting of the two nodes in the tortoise and the hare algorithm.
Both nodes will eventually enter the loop. Because one moves faster (F) than the other (S), (F) will eventually lap (S).
If the loop's start is at the list's head, (F) must meet (S) back at the list's head. This is ONLY because (F)'s speed is 2X (S)'s; if it was 3X this then would not be true. This is true because (F) completes one lap when (S) completes half a lap, so when (S) completes its first lap, (F) has completed two laps - and is back at the start of the loop with (S).
If the loop's start is NOT at the list's head, then by the time (S) enters the loop, (F) has had a head start of (k) nodes in the loop. Because (S)'s speed is only one node at a time, it will meet (F) at (k) nodes from the loop's start - as in, (k) more steps before reaching the start, NOT (k) steps AFTER the start. This would NOT be true if (S)'s speed was not one and the speed ratio was not 2:1 between (F) and (S).
3.1. This is where it gets a little tricky to explain. We can agree that (F) will continue lapping (S) until they eventually meet (see 1 above), but why at (k) nodes from the loop's start? Consider the following equation where M is the number of nodes or distance of the loop and k is the head start (F) had; the equation represents distance traveled by (F) given time t on the left in terms of distance traveled by (S) on the right:
d_F(t) = 2 * d_S(t) + k
So when (S) enters the loop and has traveled 0 distance in the loop, (F) has traveled only the (k) distance. By the time d_S = M - k, d_F = 2M - k. Because we also have to use modular math in consideration that M represents the total distance of a single lap in the loop, the POSITION of (F) and (S) at any whole M (no remainder) is 0. So then in terms of POSITION (or the differential), this leaves k (or rather, -k).
And so finally, (S) and (F) will meet at position (0 - k), or (k) nodes away from the start of the loop.
Given [3] above, as (k) represents the head start (F) had, and as (F) had traveled 2X the distance (S) traveled to enter the loop from the head of the list, (k) also represents the distance from the list's start, which then represents the start of the loop.
It's a bit late here so I hope I've articulated effectively. Let me know otherwise and I'll try and update my response.
如果使用身份哈希映射(例如 IdentityHashMap) 是允许的,这非常容易解决。不过,它确实使用了更多空间。
遍历节点列表。对于遇到的每个节点,将其添加到身份映射中。如果该节点已存在于身份映射中,则列表具有循环链接,并且在此冲突之前的节点是已知的(在移动之前检查或保留“最后一个节点”)——只需根据需要设置“下一个”即可打破循环。
遵循这个简单的方法应该是一个有趣的练习:代码留给读者作为练习。
快乐编码。
If using an identity hash map (such as IdentityHashMap) is permissible this is terribly easy to solve. It does use more space, though.
Traverse the nodes list. For each node encountered, add it to the identity map. If the node already existed in the identity map then the list has a circular link and the node which was prior to this conflict is known (either check before moving or keep the "last node") -- simply set "next" as appropriate to break the cycle.
Following this simple approach should be a fun exercise: code is left as an exercise for the reader.
Happy coding.
在链表的每个节点之后插入虚拟节点,并在插入之前检查下一个节点是否为虚拟节点。如果 next to next 是虚拟的,则在该节点的 next 中插入 null。
Insert dummy node after every node of link list and before inserting check that node next to next is dummy or not. If next to next is dummy, insert null in next of that node.
:)GlbMP
:)GlbMP
最简单且独特的方法
为了解决这个问题,我们只需计算节点数(就是这样)。 我敢打赌,到目前为止,您还没有在任何竞争性网站上看到过这个解决方案,因为我今天自己做到了......
它是如何工作的:
时间复杂度:O(n)...空间复杂度:O(n)
如果您认为它是独一无二的,请投票。
Easiest and unique way
To solve this problem we just count no of nodes (that's it). I bet you haven't seen this solution till now in any competitive website, because i made it today on my own...
How it works:
TIME COMPLEXITY: O(n)...SPACE COMPLEXITY: O(n)
UPVOTE IT IF YOU THINK IT IS UNIQUE.