Doubly Linked List Structure and Node Design in DSA Rust - Time & Space Complexity
We want to understand how the time to create and link nodes in a doubly linked list grows as we add more nodes.
How does the work change when the list gets bigger?
Analyze the time complexity of the following code snippet.
struct Node {
value: i32,
prev: Option>,
next: Option>,
}
impl Node {
fn new(value: i32) -> Self {
Node { value, prev: None, next: None }
}
}
This code defines a node for a doubly linked list with pointers to previous and next nodes.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Creating and linking each node with references to previous and next.
- How many times: Once per node added to the list.
Each new node requires a fixed amount of work to create and link.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 node creations and linkings |
| 100 | 100 node creations and linkings |
| 1000 | 1000 node creations and linkings |
Pattern observation: The work grows directly with the number of nodes added.
Time Complexity: O(n)
This means the time to build the list grows linearly with the number of nodes.
[X] Wrong: "Linking nodes takes constant time regardless of list size."
[OK] Correct: While linking one node is constant time, building the whole list requires linking each node, so total time grows with the number of nodes.
Understanding how node creation and linking scales helps you explain list operations clearly and confidently in interviews.
"What if we added a loop to search for a node before linking? How would the time complexity change?"
Practice
Solution
Step 1: Understand node pointers in doubly linked list
A doubly linked list node has two pointers: one to the next node and one to the previous node.Step 2: Compare traversal capabilities
This allows moving forward and backward through the list, unlike singly linked lists which only allow forward traversal.Final Answer:
It allows traversal in both forward and backward directions -> Option AQuick Check:
Doubly linked list = two-way traversal [OK]
- Thinking it uses less memory than singly linked list
- Assuming it only allows forward traversal
- Confusing pointer count with memory usage
Solution
Step 1: Understand ownership and pointers in Rust doubly linked list
Rust's ownership rules prevent two owning pointers to the same data. The next pointer can own the next node using Box, but the prev pointer must be a raw pointer or weak reference to avoid ownership conflicts.Step 2: Analyze options for prev pointer
struct Node { value: i32, next: Option, prev: Option<*mut Node> } uses Option<*mut Node> (a raw mutable pointer) for prev, which is common in unsafe doubly linked list implementations. struct Node { value: i32, next: Option, prev: Option } incorrectly uses Box for prev, causing double ownership. struct Node { value: i32, next: Option, prev: Option } uses Rc which is for shared ownership but not mutable by default. struct Node { value: i32, next: Option<Node>, prev: Option<Node> } uses Option<Node> which is invalid because Node is not sized.Final Answer:
struct Node { value: i32, next: Option, prev: Option<*mut Node> } -> Option AQuick Check:
Prev pointer = raw pointer to avoid ownership [OK]
- Using Box for both next and prev causing ownership errors
- Using Rc without mutability for prev
- Trying to store Node directly without Box or pointer
struct Node {
value: i32,
next: Option,
prev: Option<*mut Node>,
}
fn main() {
let mut first = Box::new(Node { value: 1, next: None, prev: None });
let mut second = Box::new(Node { value: 2, next: None, prev: None });
second.prev = Some(&mut *first as *mut _);
first.next = Some(second);
println!("{} -> {}", first.value, first.next.as_ref().unwrap().value);
}Solution
Step 1: Understand node creation and linking
Two nodes are created: first with value 1, second with value 2. The second node's prev pointer is set to point to first. The first node's next pointer is set to own second.Step 2: Analyze the print statement
The print accesses first.value (1) and first.next's value (2). Since first.next is Some(second), unwrap() succeeds and prints "1 -> 2".Final Answer:
1 -> 2 -> Option DQuick Check:
First node value then next node value = 1 -> 2 [OK]
- Confusing prev pointer direction
- Expecting compilation error due to unsafe pointer
- Assuming runtime panic without dereferencing prev
struct Node {
value: i32,
next: Option,
prev: Option,
}Solution
Step 1: Identify ownership problem with prev pointer
Using Box for both next and prev means both own the node, causing double ownership which Rust disallows.Step 2: Fix by changing prev pointer type
Changing prev to Option<*mut Node> uses a raw pointer, which does not own the node and avoids ownership conflicts.Final Answer:
Change prev to Option<*mut Node> to avoid double ownership -> Option CQuick Check:
Prev pointer must not own node to prevent ownership error [OK]
- Using Box for prev causing compile errors
- Trying to remove prev pointer losing backward traversal
- Using Option<Node> which is invalid due to size
Solution
Step 1: Understand ownership and mutability requirements
Multiple owners require reference counting (Rc). Mutable access requires interior mutability (RefCell).Step 2: Choose pointer types for next and prev
Next pointer uses Rc to own and allow mutation. Prev pointer uses Weak to avoid strong reference cycles and memory leaks.Final Answer:
Use Rc for next and Weak for prev -> Option BQuick Check:
Rc + RefCell + Weak = safe mutable doubly linked list [OK]
- Using Box for both pointers causing ownership errors
- Using raw pointers risking unsafe code
- Ignoring Weak to prevent reference cycles
Ready to prove it?
Pass the test and this pattern moves unknown
