Skip to content

Commit bbc47c3

Browse files
authored
avc278 - 2.08 - Javascript (#75)
1 parent a647d43 commit bbc47c3

File tree

1 file changed

+129
-0
lines changed
  • JavaScript/chapter02/p08_loop_detection

1 file changed

+129
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Loop Detection: Given a circular linked list, implement an algorithm that returns the node at the beginning of the
2+
// loop.
3+
// Example: A -> B -> C -> D
4+
// ^ |
5+
// | v
6+
// | <- E
7+
// Output: C
8+
const assert = require("assert");
9+
const { LinkedListNode } = require("../../lib/avc278/linkedlist");
10+
11+
/**
12+
*
13+
* @param {LinkedListNode} list input linked list in which to find loop
14+
* @return {LinkedListNode} the node in the linked list that starts the loop
15+
*
16+
* This problem can be solved through an implementation of Floyd's cycle detecting algorithm, which runs in O(N) and has
17+
* O(1) space complexity. We only keep track of slow/fast pointers in the linked list, so we use O(1) additional space.
18+
* In terms of runtime complexity, we only iterate through the entire linked list one full time to find out where the
19+
* slow and fast runners intersect. Knowing the slow and fast runners intersect is the crucial first step in knowing
20+
* which node begins the looping cycle.Say for example you have two runners in a track, where the slow runner goes 1m/s
21+
* and the fast runner goes 2m/s, and where the end is the same as the start, or in our case, the "last" node is linked
22+
* back to the first node in the linked list. We can determine the node that starts the loop by restarting the slow
23+
* runner to the beginning of the race, then have both the slow and fast runner travel at the same pace of 1m/s. Here,
24+
* they will both collide on the same node (the starting/finishing line of the track) at the same instance,
25+
* and that will be the node that starts the cycle. Alternatively, if we modify the track field so the last meter in the
26+
* race is a never-ending circle, we can apply the same logic as above to find the node the begins the cycle. And lastly
27+
* if we were to put the cycle start somewhere in the middle of the track, the logic isn't so intuitive. So where is the
28+
* magic behind this approach? It just boils down to solving a system of equations:
29+
*
30+
* a -> b -> c -> d -> e -> |
31+
* ^ v
32+
* | <- g <- f
33+
*
34+
* In the above cycle, we start the slow and fast runners at the head "a". The slow runner travels one node at a time,
35+
* and the fast runner travels two nodes at a time. Once both runners enter the cycle, whose distance from the head node
36+
* is of length H, they are guaranteed to intersect at some node in the cycle, whose distance from the cycle start is of
37+
* length I, after looping through the cycle of length C. The slow runner will loop through the cycle S times, and the
38+
* fast runner will loop through the cycle F times before they intersect.
39+
* We can state this as follows:
40+
*
41+
* H + SC + I = 2 * (H + FC + I)
42+
* H + SC + I = 2H + 2FC + 2I
43+
* SC - 2FC = H + I
44+
* C(S - 2F) = H + I
45+
* S - 2F is an integer, C is an integer, and (H + I) is a multiple of C, which is also an integer
46+
* stating that there exists a node where the slow and fast runners intersect, but also there is a node they must both
47+
* reach that starts the cycle.
48+
*
49+
* The next step here is to acknowledge that if we reset the slow runner to the beginning and make both runners travel
50+
* H nodes, both nodes will be in the cycle, and that after some multiple of C cycles, this can be reduced to the simple
51+
* problem from above where two runners are running around a track where the start node proceeds the end node, except
52+
* the runners are starting at different nodes, but will reach the starting node at the same instance.
53+
*
54+
* Runtime: O(N)
55+
* Space: O(1)
56+
*
57+
*/
58+
const loopDetection = (list) => {
59+
let slow = list;
60+
let fast = list;
61+
62+
do {
63+
slow = slow.next;
64+
fast = fast.next.next;
65+
} while (slow !== fast);
66+
slow = list;
67+
68+
while (slow !== fast) {
69+
slow = slow.next;
70+
fast = fast.next;
71+
}
72+
73+
return slow;
74+
};
75+
76+
describe(module.filename, () => {
77+
it("should return the head of the linked list when the tail points to the head.", () => {
78+
/*
79+
a1 -> a2 -> a3 -> |
80+
^ v
81+
| a7 <- a6 <- a5 <- a4
82+
*/
83+
const a7 = new LinkedListNode(7);
84+
const a6 = new LinkedListNode(6, a7);
85+
const a5 = new LinkedListNode(5, a6);
86+
const a4 = new LinkedListNode(4, a5);
87+
const a3 = new LinkedListNode(3, a4);
88+
const a2 = new LinkedListNode(2, a3);
89+
const a1 = new LinkedListNode(1, a2);
90+
a7.next = a1;
91+
92+
assert.strictEqual(loopDetection(a1), a1);
93+
});
94+
it("should return the tail of the linked list when the tail points to itself.", () => {
95+
/*
96+
a1 -> a2 -> a3 -> a4 -> a5 -> a6 -> a7 -> |
97+
^ v
98+
| < -
99+
*/
100+
const a7 = new LinkedListNode(7);
101+
const a6 = new LinkedListNode(6, a7);
102+
const a5 = new LinkedListNode(5, a6);
103+
const a4 = new LinkedListNode(4, a5);
104+
const a3 = new LinkedListNode(3, a4);
105+
const a2 = new LinkedListNode(2, a3);
106+
const a1 = new LinkedListNode(1, a2);
107+
a7.next = a7;
108+
109+
assert.strictEqual(loopDetection(a1), a7);
110+
});
111+
it("should return some node in the middle that starts the loop.", () => {
112+
/*
113+
a1 -> a2 -> a3 -> a4 -> a5 -> |
114+
^ v
115+
| <- a7 <- a6
116+
*/
117+
118+
const a7 = new LinkedListNode(7);
119+
const a6 = new LinkedListNode(6, a7);
120+
const a5 = new LinkedListNode(5, a6);
121+
const a4 = new LinkedListNode(4, a5);
122+
const a3 = new LinkedListNode(3, a4);
123+
const a2 = new LinkedListNode(2, a3);
124+
const a1 = new LinkedListNode(1, a2);
125+
a7.next = a4;
126+
127+
assert.strictEqual(loopDetection(a1), a4);
128+
});
129+
});

0 commit comments

Comments
 (0)