访谈:删除链表中的循环-Java
我在面试中被问到这样一个问题:“如何检测链表中的循环?”我解决了这个问题,但面试官马上问我如何删除链表中的循环。我摸索着 所以任何关于如何解决这个问题的指针,可能是伪代码,或者是方法定义 我对Java很熟悉,所以我将这个问题标记在Java下 例如,这个链表有一个循环访谈:删除链表中的循环-Java,java,data-structures,linked-list,Java,Data Structures,Linked List,我在面试中被问到这样一个问题:“如何检测链表中的循环?”我解决了这个问题,但面试官马上问我如何删除链表中的循环。我摸索着 所以任何关于如何解决这个问题的指针,可能是伪代码,或者是方法定义 我对Java很熟悉,所以我将这个问题标记在Java下 例如,这个链表有一个循环 0--->1---->2---->3---->4---->5---->6 ▲ | |
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
0-->1-->2-->3-->4-->5-->6
▲ |
| ▼
11这个问题有两个部分:
检测列表中是否有循环
确定循环的开始
一旦知道循环的开始位置,就很容易识别列表中的最后一个元素,因为它是列表中循环开始之后的元素,最后指向循环的开始。然后,将该元素的下一个指针/引用设置为null
,以更正循环链接列表(不是循环链接列表,最后一个元素指向第一个元素-这将是循环列表的一个特定实例),这很简单
因为它涉及到使用两个以不同速度移动的指针/参考,所以这是检测循环的一种方法。如果有一个循环,两个指针(例如p1
和p2
)将在有限的步骤后指向同一个元素。有趣的是,可以证明,它们相遇的元素到循环开始的距离(继续以相同的前进方向遍历列表)与循环开始到列表的头部的距离相同。也就是说,如果列表的线性部分有k
元素,那么两个指针将在长度m
的循环内的一个点m-k
处相遇,从循环的开始或k
元素到循环的“结束”(当然,这是一个循环,所以它没有“结束”-它再次只是“开始”)。这给了我们一种找到循环起点的方法:
一旦检测到循环,让p2
保持指向上面步骤的循环终止的元素,但重置p1
,使其指向列表的开头。现在,将每个指针一次移动一个元素。由于p2
在循环内部开始,它将继续循环。在k
步数(等于循环起点到列表头部的距离)之后,p1
和p2
将再次相遇。这将为您提供循环开始的参考
现在很容易设置p1
(或p2
)以指向开始循环的元素并遍历循环,直到p1
最终指向开始元素。此时,p1
正在引用“最后一个”元素列表,它的下一个指针可以设置为null
下面是一些快速而肮脏的Java代码,假设一个节点
的链表,其中节点
有一个下一个
引用。这是可以优化的,但它应该给你一个基本的想法:
Node slow, fast, start;
fast = slow = head;
//PART I - Detect if a loop exists
while (true)
{
// fast will always fall off the end of the list if it is linear
if (fast == null || fast.next == null)
{
// no loop
return;
}
else if (fast == slow || fast.next == slow)
{
// detected a loop
break;
}
else
{
fast = fast.next.next; // move 2 nodes at at time
slow = slow.next; // move 1 node at a time
}
}
//PART II - Identify the node that is the start of the loop
fast = head; //reset one of the references to head of list
//until both the references are one short of the common element which is the start of the loop
while(fast.next != slow.next)
{
fast = fast.next;
slow = slow.next;
}
start = fast.next;
//PART III - Eliminate the loop by setting the 'next' pointer
//of the last element to null
fast = start;
while(fast.next != start)
{
fast = fast.next;
}
fast.next = null; //break the loop
可能有助于解释第二部分背后的原因:
假设循环的长度为M,
其余部分的长度是多少
链表是L,让我们算出
循环中的位置是什么
t1/t2首次见面
定义循环中的第一个节点是
位置0,按照我们创建的链接
位置1,2,…,直到M-1。(
当我们在循环中行走时,我们的电流
位置为(行走长度)M型,
对吗?)假设t1/t2在
位置p,则其行程时间为
相同,(L+k1*M+p)/v=(L+k2*M+p)/2v
对于一些k1
因此得出结论,如果t1从
p、 t2从头部开始,在头部移动
同样的速度,然后将被授予满足
在位置0处,该节点的第一个节点
周期QED
更多参考资料:
- 还引用了采访书中的解释
如果允许使用标识哈希映射(如),那么这非常容易解决。不过,它确实占用了更多的空间
遍历节点列表。对于遇到的每个节点,将其添加到标识映射中。如果该节点已存在于标识映射中,则该列表具有循环链接,并且该冲突之前的节点是已知的(在移动之前检查或保留“最后一个节点”)--只需根据需要设置“下一个”即可打破该循环
遵循这个简单的方法应该是一个有趣的练习:代码留给读者作为练习
快乐编码。解决方案1-由以下人员提供:
此解决方案的解释直接来自于本书:
如果我们移动两个指针,一个带
速度1和另一个速度2,它们
如果链接到
列表有一个循环。为什么?想两个
在轨道上行驶的汽车;更快的车
总是会通过慢一点的
这里棘手的部分是找到起点
循环的一部分。打个比方,想象一下,
两个人在跑道上赛跑,
一个跑得比另一个快两倍
其他的。如果他们同时出发
地点,他们下次什么时候见面?他们
下一次会议将在会议开始时举行
下一圈
现在,让我们假设快跑运动员在比赛中领先k米
一个n步的圈。他们下一次什么时候来
满足他们将在9米前相遇
下一圈的开始。(为什么?快
跑步者会得到k+2(n-k)
步骤,包括其开头,以及
跑得慢的人会得到n-k
两个步骤都将是
循环的开始)
现在,回到问题,当快跑者(n2)和
慢跑者(n1)在我们的周围移动
循环链表,n2将有一个
当n1时,在循环中领先
进入。具体来说,它将有一个
k的开头,其中k是数字
节点数
public static LinkedListNode findStartOfLoop(LinkedListNode head) {
LinkedListNode n1 = head;
LinkedListNode n2 = head;
// find meeting point using Tortoise and Hare algorithm
// this is just Floyd's cycle detection algorithm
while (n2.next != null) {
n1 = n1.next;
n2 = n2.next.next;
if (n1 == n2) {
break;
}
}
// Error check - there is no meeting point, and therefore no loop
if (n2.next == null) {
return null;
}
/* Move n1 to Head. Keep n2 at Meeting Point. Each are k steps
/* from the Loop Start. If they move at the same pace, they must
* meet at Loop Start. */
n1 = head;
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
// Now n2 points to the start of the loop.
return n2;
}
public static LinkedListNode findHeadOfLoop(LinkedListNode head) {
int indexer = 0;
Map<LinkedListNode, Integer> map = new IdentityHashMap<LinkedListNode, Integer>();
map.put(head, indexer);
indexer++;
// start walking along the list while putting each node in the HashMap
// if we come to a node that is already in the list,
// then that node is the start of the cycle
LinkedListNode curr = head;
while (curr != null) {
if (map.containsKey(curr.next)) {
curr = curr.next;
break;
}
curr = curr.next;
map.put(curr, indexer);
indexer++;
}
return curr;
}
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
0-->D->1-->D->2-->D->3->D-->4->D-->5->D-->6
▲ |
/ ▼
11<—D<-22<—D<-12<—D<-9<—D<--8
Node(11)->next->next == D
Node(11)->next =null
//Find a Loop in Linked List and remove link between node
public void findLoopInList() {
Node fastNode = head;
Node slowNode = head;
boolean isLoopExist = false;
while (slowNode != null && fastNode != null && fastNode.next != null) {
fastNode = fastNode.next.next;
slowNode = slowNode.next;
if (slowNode == fastNode) {
System.out.print("\n Loop Found");
isLoopExist = true;
break;
}
}
if (isLoopExist) {
slowNode = head;
Node prevNode = null;
while (slowNode != fastNode) {
prevNode = fastNode;
fastNode = fastNode.next;
slowNode = slowNode.next;
}
System.out.print("Loop Found Node : " + slowNode.mData);
prevNode.next = null; //Remove the Loop
}
}
void removeTheLoop(Node *root)
{
std :: unordered_set < struct Node * > s;
if(root == NULL)
return ;
s.insert(root);
int before_size = s.size();
while(1)
{
if(root -> next == NULL)
return;
s.insert(root -> next);
if(before_size == s.size())
{
root -> next = NULL;
return;
}
before_size = s.size();
root = root -> next;
}
}