Detect a cycle with two pointers
Floyd's tortoise and hare: find a loop in a linked list with no extra memory.
- Write Floyd's cycle check with slow and fast pointers.
- Explain why both fast and fast.Next must be checked for nil.
- Predict where slow and fast meet on a small cyclic list.
The problem
Given the head of a linked list, return true if some node's Next points back to an earlier node, so walking it would never end.
Brute force first
Remember every node you visit in a map[*Node]bool. If you reach a node already in the map, there is a cycle; if you reach nil, there is not.
O(n) time, O(n) space. Correct and easy to explain; the follow-up is always "now do it in O(1) space".
The approach
The easy answer is a map[*Node]bool of visited nodes: O(n) time, O(n) space. Say it first.
Then the better one. Move slow one step and fast two steps. With no loop, fast reaches nil. With a loop, fast gains one node per step on slow and must land on it. O(n) time, O(1) space.
package mainimport "fmt"type Node struct { Val int Next *Node}// hasCycle is Floyd's tortoise and hare: slow moves one step, fast moves two.// If there is a loop, fast laps slow and they meet. If not, fast hits nil.func hasCycle(head *Node) bool { slow, fast := head, head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next if slow == fast { // pointer comparison: the same node, not the same value return true } } return false}func main() { a := &Node{Val: 1} b := &Node{Val: 2} c := &Node{Val: 3} a.Next, b.Next = b, c fmt.Println(hasCycle(a)) // false c.Next = b // 3 points back to 2 fmt.Println(hasCycle(a)) // true fmt.Println(hasCycle(nil)) // false}Dry run
1 → 2 → 3 → 4, and 4 points back to 2. Slow moves 1, fast moves 2:
| step | slow | fast | same node? |
|---|---|---|---|
| start | 1 | 1 | (not checked) |
| 1 | 2 | 3 | no |
| 2 | 3 | 2 (3 → 4 → 2) | no |
| 3 | 4 | 4 (2 → 3 → 4) | yes, return true |
Go-specific gotchas
- Check `fast != nil && fast.Next != nil` before
fast.Next.Next. Both are needed, or you get a nil pointer panic on even-length lists. - `slow == fast` compares pointers, which means "the same node". Comparing
slow.Val == fast.Valis a bug when values repeat. - Pointers are valid map keys, so
map[*Node]boolworks for the simple version.
- Map of visited nodes first, then two pointers for O(1) space.
- Guard both
fastandfast.Next. - Compare nodes, not values.
hasCycle(head *Node) bool that reports whether the list loops back on itself, using Floyd's slow and fast pointers in O(1) extra space (the hidden tests check that it allocates nothing, so no "visited" map). Example: 1 → 2 → 3 → back to 2 → true.Tab indents. Press Esc, then Tab, to leave the editor.
- 1The main task: Return the node where the cycle starts. Hint: after they meet, move one pointer back to the head and step both by one.
- 2Dry run Floyd on
1 → 2 → 3with no cycle. Why does the loop stop, and after how many steps? - 3Find the length of the cycle, once you know there is one.