Insert at End of Circular Linked List in DSA Rust - Time & Space Complexity
We want to understand how the time needed to insert a new item at the end of a circular linked list changes as the list grows.
Specifically, how does the number of steps grow when the list gets bigger?
Analyze the time complexity of the following code snippet.
struct Node {
data: i32,
next: Option>,
}
fn insert_end(head: &mut Option>, value: i32) {
let mut new_node = Box::new(Node { data: value, next: None });
if head.is_none() {
new_node.next = Some(new_node.clone());
*head = Some(new_node);
return;
}
let mut temp = head.as_mut().unwrap();
while temp.next.as_ref().unwrap().as_ref() as *const _ != head.as_ref().unwrap().as_ref() as *const _ {
temp = temp.next.as_mut().unwrap();
}
new_node.next = head.take();
temp.next = Some(new_node);
*head = temp.next.clone();
}
This code inserts a new node at the end of a circular linked list by traversing the list to find the last node.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop that moves through each node to find the last node.
- How many times: It runs once for each node until it reaches the last node, so it runs n times for n nodes.
As the list grows, the number of steps to find the last node grows in a straight line with the number of nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to find the end |
| 100 | About 100 steps to find the end |
| 1000 | About 1000 steps to find the end |
Pattern observation: The steps increase directly with the number of nodes, so doubling the list size doubles the work.
Time Complexity: O(n)
This means the time to insert at the end grows linearly with the list size; more nodes mean more steps.
[X] Wrong: "Inserting at the end is always quick and takes the same time no matter the list size."
[OK] Correct: Because we must find the last node by moving through the list, the time depends on how many nodes there are.
Understanding this helps you explain how linked lists work and why some operations take longer as data grows, a key skill in coding interviews.
"What if we kept a pointer to the last node? How would the time complexity change?"
Practice
Solution
Step 1: Understand the structure of a circular linked list
A circular linked list connects the last node back to the first node, forming a circle.Step 2: Compare with a regular singly linked list
A regular singly linked list ends with a node pointing to null, not back to the first node.Final Answer:
In a circular linked list, the last node points back to the first node. -> Option AQuick Check:
Circular linked list last node points to head [OK]
- Thinking circular lists have two pointers per node
- Confusing circular linked list with doubly linked list
- Assuming circular lists cannot have end insertions
Solution
Step 1: Create new node and link to head
In a circular linked list, the new node's next should point to head to maintain the circle.Step 2: Update tail's next and tail pointer
Tail's next should point to the new node, and tail should be updated to new node.Final Answer:
let new_node = Node::new(data); new_node.next = head; tail.next = Some(new_node); tail = new_node; -> Option BQuick Check:
New node points to head, tail updated [OK]
- Setting new node's next to None breaks the circle
- Linking new node to tail instead of head
- Not updating tail pointer after insertion
fn print_list(head: &Option) {
let mut temp = head.as_ref();
if temp.is_none() { return; }
let start = temp.unwrap();
loop {
print!("{} -> ", temp.unwrap().data);
temp = temp.unwrap().next.as_ref();
if temp.is_none() || temp.unwrap().data == head.as_ref().unwrap().data { break; }
}
println!("null");
}
// After insertion of 4 at end
// List structure: 1 -> 2 -> 3 -> 4 -> (back to 1)
Solution
Step 1: Understand the print loop logic
The loop prints nodes until it reaches the head again, then breaks to avoid infinite loop.Step 2: Trace the list after insertion
After inserting 4 at end, list is 1 -> 2 -> 3 -> 4 -> back to 1. The print stops before repeating head.Final Answer:
1 -> 2 -> 3 -> 4 -> null -> Option CQuick Check:
Print stops before repeating head [OK]
- Printing the head node twice causing infinite loop
- Not breaking loop on head node detection
- Confusing order of nodes after insertion
fn insert_end(tail: &mut Option, data: i32) {
let mut new_node = Box::new(Node::new(data));
new_node.next = None;
if tail.is_none() {
*tail = Some(new_node);
tail.as_mut().unwrap().next = tail.clone();
} else {
new_node.next = tail.as_ref().unwrap().next.clone();
tail.as_mut().unwrap().next = Some(new_node);
*tail = tail.as_mut().unwrap().next.clone();
}
}
Solution
Step 1: Analyze tail.clone() usage
Cloning tail (which is an Option) duplicates ownership, which Rust forbids at runtime for Box.Step 2: Understand Rust ownership rules
Box pointers cannot be cloned freely; this causes runtime panic or compile error due to ownership violation.Final Answer:
Using tail.clone() causes ownership issues leading to runtime panic. -> Option AQuick Check:
Cloning Box without proper ownership causes error [OK]
- Setting next to None breaking circularity
- Ignoring Rust ownership and cloning rules
- Not updating tail pointer correctly
Solution
Step 1: Understand sorted circular linked list insertion
To keep the list sorted, find the correct position where 25 fits between nodes.Step 2: Insert node at correct position and update links
Traverse nodes until finding node with value less than 25 and next node greater than 25, then insert new node there.Step 3: Update tail if inserted at end
If inserted after the current tail, update tail pointer to new node to maintain list integrity.Final Answer:
Traverse the list to find the correct position and insert 25 there, updating links accordingly. -> Option DQuick Check:
Insert at sorted position to keep order [OK]
- Inserting at end without sorting breaks order
- Replacing node value loses data integrity
- Not updating tail when inserting at end
Ready to prove it?
Pass the test and this pattern moves unknown
