Insert at Beginning of Circular Linked List in DSA Rust - Time & Space Complexity
We want to know how the time needed changes when we add a new node at the start of a circular linked list.
How does the work grow as the list gets bigger?
Analyze the time complexity of the following code snippet.
#[derive(Clone)]
struct Node {
data: i32,
next: Option>,
}
fn insert_at_beginning(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 adds a new node at the start of a circular linked list by updating pointers to keep the circle intact.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: A loop that moves through the list to find the last node.
- How many times: It runs once for each node until it reaches the last node, so it depends on the list size.
As the list grows, the loop takes longer because it must find the last node before inserting.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to find last node |
| 100 | About 100 steps to find last node |
| 1000 | About 1000 steps to find last node |
Pattern observation: The work grows directly with the number of nodes; doubling nodes doubles the steps.
Time Complexity: O(n)
This means the time to insert grows linearly with the list size because we must find the last node each time.
[X] Wrong: "Inserting at the beginning is always fast and takes constant time."
[OK] Correct: In a circular linked list without a tail pointer, we must find the last node first, which takes time proportional to the list size.
Understanding this helps you explain how pointer updates affect performance in linked structures, a key skill in many coding challenges.
"What if we kept a tail pointer to the last node? How would the time complexity change?"
Practice
Solution
Step 1: Understand circular linked list structure
In a circular linked list, the last node points back to the head node, forming a circle.Step 2: Insert new node at beginning
The new node is linked to the current head, and the last node's next pointer is updated to this new node, making it the new head.Final Answer:
The new node becomes the head and points to the old head, maintaining the circular link. -> Option DQuick Check:
New head points to old head, circle intact [OK]
- Forgetting to update last node's next pointer
- Breaking the circular link accidentally
- Replacing the last node instead of adding new head
Solution
Step 1: Create new node pointing to current head
The new node's next pointer should point to the current head to maintain the list.Step 2: Update last node's next to new node and update head
Since it's circular, last node's next must point to new node, then head is updated to new node.Final Answer:
let new_node = Node { data: val, next: head.clone() }; last.next = Some(new_node.clone()); head = Some(new_node); -> Option AQuick Check:
New node next = head, last next = new node, head updated [OK]
- Not updating last node's next pointer
- Setting new node's next to None
- Not cloning nodes properly causing ownership errors
struct Node {
data: i32,
next: Option,
}
fn print_list(head: &Option) {
if head.is_none() { return; }
let mut temp = head.as_ref();
loop {
if let Some(node) = temp {
print!("{} -> ", node.data);
temp = node.next.as_ref();
if temp == head.as_ref() { break; }
}
}
println!("null");
}
fn insert_beginning(head: &mut Option, val: i32) {
let mut new_node = Box::new(Node { data: val, 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() != head.as_ref().unwrap().as_ref() {
temp = temp.next.as_mut().unwrap();
}
new_node.next = head.take();
temp.next = Some(new_node);
*head = temp.next.clone();
}
fn main() {
let mut head = None;
insert_beginning(&mut head, 10);
insert_beginning(&mut head, 20);
insert_beginning(&mut head, 30);
print_list(&head);
}Solution
Step 1: Trace insertions in order
Insert 10 first: list is 10 -> 10 (single node points to itself). Insert 20: new node 20 points to 10, last node (10) points to 20, head updated to 20. Insert 30: new node 30 points to 20, last node (10) points to 30, head updated to 30.Step 2: Print list starting from head
Starting at 30, next is 20, then 10, then back to 30 (stop). So printed: 30 -> 20 -> 10 -> null.Final Answer:
30 -> 20 -> 10 -> null -> Option BQuick Check:
New nodes inserted at head, order reversed [OK]
- Confusing order of nodes after insertion
- Not updating last node's next pointer correctly
- Incorrect loop condition in print function
fn insert_beginning(head: &mut Option, val: i32) {
let mut new_node = Box::new(Node { data: val, 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() != head.as_ref().unwrap().as_ref() {
temp = temp.next.as_mut().unwrap();
}
new_node.next = head.take();
temp.next = Some(new_node);
*head = temp.next.clone();
}Solution
Step 1: Analyze cloning of new_node
Cloning new_node inside new_node.next = Some(new_node.clone()) causes recursive cloning because new_node contains next which contains new_node again, leading to infinite recursion.Step 2: Understand Rust cloning behavior
Rust's clone on a Box<Node> will recursively clone the entire structure, so cloning a node that points to itself causes stack overflow.Final Answer:
Cloning new_node causes infinite recursion and stack overflow. -> Option CQuick Check:
Self-referential clone causes infinite recursion [OK]
- Ignoring cloning impact on self-referential structures
- Assuming head.take() deletes nodes permanently
- Misreading loop condition as infinite
fn insert_beginning(head: &mut Option, val: i32) {
let mut new_node = Box::new(Node { data: val, next: None });
if head.is_none() {
// Case 1: Empty list
new_node.next = Some(new_node); // Problem here
*head = Some(new_node);
return;
}
// Case 2: Non-empty list
let mut temp = head.as_mut().unwrap();
while temp.next.as_ref().unwrap().as_ref() != head.as_ref().unwrap().as_ref() {
temp = temp.next.as_mut().unwrap();
}
new_node.next = head.take();
temp.next = Some(new_node);
*head = temp.next.clone();
}What is the correct fix for Case 1 to avoid cloning and infinite recursion?Solution
Step 1: Understand self-referential node problem
Assigning new_node.next = Some(new_node) tries to move new_node into itself, which is invalid and causes infinite recursion if cloned.Step 2: Use Rc or raw pointers
Rust's safe way to handle self-references is using Rc (reference counting) and RefCell (interior mutability) to create cyclic references without cloning issues.Step 3: Avoid unsafe code unless necessary
Using unsafe code is error-prone; Rc<RefCell> is the recommended safe approach.Final Answer:
Use a raw pointer or Rc to create self-reference safely. -> Option AQuick Check:
Safe self-reference needs Rc/RefCell or raw pointers [OK]
- Assigning node to itself directly causing move errors
- Using None breaks circular property
- Using unsafe code without understanding risks
Ready to prove it?
Pass the test and this pattern moves unknown
