Pergunta de entrevista da empresa Esri

What's the best way to detect a loop in a linked list?

Respostas da entrevista

Sigiloso

20 de mar. de 2012

I'll rather make a doubly link list first (easy to navigate both directions) struct node { var element; node* next; node* previous; } *head; //declare head pointer write stub method to set it bool isloop(LList) { if (!isempty(LList) //check if list is not empty { Node *pointer=head; // define pointer setting to head while(pointer.next -> null) //iterate through list from head to tail { if(pointer.next == pointer.previous) //its a loop : next is pointing to previous return true; } return false; // return false if condition is not meat } //end if } //end while } // end method

Sigiloso

20 de mar. de 2012

Keep a hash set of all nodes seen so far O(n) time complexity, O(n) space complexity Keeping a set of all the nodes have seen so far and testing to see if the next node is in that set would be a perfectly correct solution. It would run fast as well. However it would use enough extra space to make a copy of the linked list. Allocating that much memory is prohibitively expensive for large lists. // Inefficient solution function boolean hasLoop(Node startNode){ HashSet nodesSeen = new HashSet(); Node currentNode = startNode; do { if (nodesSeen.contains(currentNode)) return true; nodesSeen.add(currentNode); } while (currentNode = currentNode.next()); return false; }

Sigiloso

20 de mar. de 2012

Use a doubly linked list O(n) time complexity Doubly linked lists make it easy to tell if there is a loop. If you encounter any node that doesn't link to the last node you visited, you know that there are two nodes linking to that node. Because the back links could be initially messed up in some other way, this algorithm is only correct if you can trust the back links. Otherwise it is just a malformed doubly linked list finder. The singly linked list can even be converted into a doubly linked list with little additional work. Again this will require that we change the structure of the Node to accomodate a second link. Something that may not be possible in all cases. Usually a singly linked list is used because the amount of space to allocate for each node is at a premium. // Inefficient solution function boolean hasLoop(Node startNode){ Node currentNode = startNode; Node previousNode = null; do { if (previousNode && currentNode.prev() && previousNode != currentNode.prev()) return true; if (!currentNode.prev()) currentNode.setPrev(previousNode); previousNode = currentNode; } while (currentNode = currentNode.next()); return false; }

Sigiloso

20 de mar. de 2012

Check the Entire List So Far O(n^2) time complexity For each node, assume that the portion of the list examined so for has no loops and check to see if the next node creates a loop by iterating again over the entire list up to that point. // Inefficient solution function boolean hasLoop(Node startNode){ Node currentNode = startNode.next(); int i=0; do { Node checkNode = startNode; int j=0; do { if (checkNode == currentNode) return true; j++; } while (j

Sigiloso

20 de mar. de 2012

O(n) time complexity If you reverse the list, and remember the inital node, you will know that there is a cycle if you get back to the first node. While efficient, this solution changes the list. Reversing the list twice would put the list back in its initial state, however this solution is not appropriate for multi-threaded applications. In some cases there may not be a way to modify nodes. Since changing the nodes is not needed to get the answer, this solution is not recommended. // Solution modifies the list function boolean hasLoop(Node startNode){ Node previousNode = null; Node currentNode = startNode; Node nextNode; if (!currentNode.next()) return false; while(currentNode){ nextNode = currentNode.next(); currentNode.setNext(previousNode); previousNode = currentNode; currentNode = nextNode; } return (previousNode == startNode); }

Sigiloso

20 de mar. de 2012

Use Memory Allocation Information O(n) time complexity in the amount of memory on the computer Some programming languages allow you to see meta information about each node -- the memory address at which it is allocated. Because each node has a unique numeric address, it is possible to use this information to detect cycles. For this algorithm, keep track of the minimum memory address seen, the maximum memory address seen, and the number of nodes seen. If more nodes have been seen than can fit in the address space then some node must have been seen twice and there is a cycle. // Depends on size of available computer memory rather than size of list function boolean hasLoop(Node startNode){ Node currentNode = startNode; int minAddress, int maxAddress = ¤tNode; int nodesSeen = 0; while(currentNode = currentNode.next()){ nodesSeen++; if (¤tNode maxAddress) maxAddress = ¤tNode; if (maxAddress - minAddress < nodesSeen) return true; } return false; }

Sigiloso

20 de mar. de 2012

Best Solutions Catch Larger and Larger Loops O(n) time complexity Always store some node to check. Occasionally reset this node to avoid the "Detect Only Full Loops" problem. When resetting it, double the amount of time before resetting it again. // Good solution function boolean hasLoop(Node startNode){ Node currentNode = startNode; Node checkNode = null; int since = 0; int sinceScale = 2; do { if (checkNode == currentNode) return true; if (since >= sinceScale){ checkNode = currentNode; since = 0; sinceScale = 2*sinceScale; } since++; } while (currentNode = currentNode.next()); return false; } This solution is O(n) because sinceScale grows linearly with the number of calls to next(). Once sinceScale is greater than the size of the loop, another n calls to next() may be required to detect the loop. This solution requires up to 3 traversals of the list. This solution was devised by Stephen Ostermiller and proven O(n) by Daniel Martin. Catch Loops in Two Passes O(n) time complexity Simultaneously go through the list by ones (slow iterator) and by twos (fast iterator). If there is a loop the fast iterator will go around that loop twice as fast as the slow iterator. The fast iterator will lap the slow iterator within a single pass through the cycle. Detecting a loop is then just detecting that the slow iterator has been lapped by the fast iterator. // Best solution function boolean hasLoop(Node startNode){ Node slowNode = Node fastNode1 = Node fastNode2 = startNode; while (slowNode && fastNode1 = fastNode2.next() && fastNode2 = fastNode1.next()){ if (slowNode == fastNode1 || slowNode == fastNode2) return true; slowNode = slowNode.next(); } return false; } This solution is "Floyd's Cycle-Finding Algorithm" as published in "Non-deterministic Algorithms" by Robert W. Floyd in 1967. It is also called "The Tortoise and the Hare Algorithm".