Detect if a Linked List is Circular in DSA Rust - Time & Space Complexity
We want to understand how long it takes to check if a linked list loops back on itself.
How does the time needed grow as the list gets longer?
Analyze the time complexity of the following code snippet.
fn is_circular(head: &Option>) -> bool {
let mut slow = head.as_ref();
let mut fast = head.as_ref();
while let (Some(s), Some(f)) = (slow, fast) {
fast = f.next.as_ref().and_then(|n| n.next.as_ref());
slow = s.next.as_ref();
if slow == fast {
return true;
}
}
false
}
This code uses two pointers moving at different speeds to detect a loop in the linked list.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Moving two pointers through the list nodes.
- How many times: Each pointer moves forward step by step until they meet or reach the end.
As the list grows, the pointers move through more nodes, but the fast pointer moves twice as fast.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 to 20 pointer moves |
| 100 | About 100 to 200 pointer moves |
| 1000 | About 1000 to 2000 pointer moves |
Pattern observation: The number of steps grows roughly in a straight line with the list size.
Time Complexity: O(n)
This means the time to detect a loop grows linearly with the number of nodes in the list.
[X] Wrong: "The fast pointer makes this algorithm run twice as fast, so the time complexity is O(n/2)."
[OK] Correct: We drop constants in time complexity, so O(n/2) is the same as O(n). The fast pointer helps find the loop sooner but does not change the overall growth rate.
Understanding this technique shows you can handle linked list problems efficiently, a common skill interviewers look for.
"What if we used only one pointer moving one step at a time? How would the time complexity change?"
Practice
Solution
Step 1: Understand linked list structure
A linked list is a chain of nodes where each node points to the next node.Step 2: Define circular linked list
If the last node points back to any previous node, it creates a loop, making the list circular.Final Answer:
The last node points back to one of the previous nodes, forming a loop -> Option DQuick Check:
Circular linked list = last node points back [OK]
- Thinking circular means sorted
- Confusing empty list with circular
- Assuming single node list is always circular
Solution
Step 1: Understand Rust ownership for linked lists
Rust requires using Option for next to allow nullable and heap allocation.Step 2: Check each option
struct Node { value: i32, next: Option } uses Option, which is correct. Others either lack Option or use references incorrectly.Final Answer:
struct Node { value: i32, next: Option } -> Option AQuick Check:
Use Option for next pointer in Rust [OK]
- Using raw Node type for next causes infinite size error
- Using references without lifetimes
- Omitting Option for nullable next
fn is_circular(head: &Option) -> bool {
let mut slow = head.as_ref();
let mut fast = head.as_ref();
while let (Some(s), Some(f)) = (slow, fast) {
fast = f.next.as_ref().and_then(|n| n.next.as_ref());
slow = s.next.as_ref();
if slow == fast {
return true;
}
}
false
}
// List: 1 -> 2 -> 3 -> 2 (loop back to node with value 2)
// Calling is_circular returns what?
Solution
Step 1: Understand Floyd's cycle detection
Two pointers move at different speeds; if they meet, list is circular.Step 2: Analyze given list and code
List loops back from node 3 to node 2, so slow and fast pointers will meet, returning true.Final Answer:
true -> Option BQuick Check:
Floyd's algorithm detects loop = true [OK]
- Confusing pointer movement speeds
- Not checking both pointers for null
- Assuming no loop when pointers meet
fn has_cycle(head: &Option) -> bool {
let mut slow = head.as_ref();
let mut fast = head.as_ref();
while fast.is_some() && fast.unwrap().next.is_some() {
slow = slow.unwrap().next.as_ref();
fast = fast.unwrap().next.as_ref().unwrap().next.as_ref();
if slow == fast {
return true;
}
}
false
}Solution
Step 1: Check unwrap usage safety
Using unwrap() without confirming Some can panic if null is encountered.Step 2: Identify risk in loop
fast.unwrap().next.as_ref().unwrap() assumes next exists, which may not be true at list end.Final Answer:
Using unwrap() without checking can cause panic if null -> Option CQuick Check:
Unsafe unwrap causes panic [OK]
- Ignoring unwrap panic risk
- Assuming pointers move same speed
- Not checking loop exit conditions
Solution
Step 1: Understand single-node loop problem
If a node points to itself, Floyd's algorithm may detect cycle, but edge cases can be tricky.Step 2: Use hash set for robust detection
Storing visited nodes in a hash set helps detect any repeated node, including self-loop cases.Final Answer:
Use a hash set to store visited nodes and detect repeats -> Option AQuick Check:
Hash set detects all cycles including self-loops [OK]
- Assuming Floyd's algorithm covers all edge cases
- Ignoring self-loop as cycle
- Not handling single-node circular lists
Ready to prove it?
Pass the test and this pattern moves unknown
