Doubly Linked List Structure and Node Design in DSA Python - 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.
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
new_node.prev = current
This code creates nodes and adds them to the end of a doubly linked list.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop that moves through the list to find the last node.
- How many times: It runs once for each node already in the list before adding the new one.
As the list grows, the time to add a new node grows because we must walk through all existing nodes to reach the end.
| 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 work grows directly with the number of nodes already in the list.
Time Complexity: O(n)
This means adding a node takes longer as the list gets bigger, because we must walk through all nodes to find the end.
[X] Wrong: "Adding a node is always fast and takes the same time no matter how big the list is."
[OK] Correct: Because without keeping track of the last node, we must walk through the whole list each time, so it takes longer as the list grows.
Understanding how linked list operations scale helps you explain your code choices clearly and shows you know how data structures behave in real situations.
"What if we kept a pointer to the last node in the list? How would the time complexity of adding a node change?"
Practice
Solution
Step 1: Understand node pointers in singly linked list
A singly linked list node has only one pointer to the next node.Step 2: Understand node pointers in doubly linked list
A doubly linked list node has two pointers: one to the previous node and one to the next node.Final Answer:
A doubly linked list node has pointers to both the previous and next nodes. -> Option BQuick Check:
Doubly linked list nodes have two pointers = D [OK]
- Thinking doubly linked list nodes store data differently
- Confusing pointer count between singly and doubly linked lists
- Assuming doubly linked list nodes have only one pointer
Solution
Step 1: Check for data storage and pointers
A doubly linked list node must store data and have both prev and next pointers.Step 2: Verify class definitions
class Node: def __init__(self, data): self.data = data self.prev = None self.next = None correctly defines data, prev, and next attributes in __init__.Final Answer:
class Node:\n def __init__(self, data):\n self.data = data\n self.prev = None\n self.next = None -> Option DQuick Check:
Node class with data, prev, next = B [OK]
- Omitting the prev pointer
- Not initializing data attribute
- Defining only next pointer like singly linked list
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
head = Node(1)
second = Node(2)
third = Node(3)
head.next = second
second.prev = head
second.next = third
third.prev = second
current = third
result = []
while current:
result.append(current.data)
current = current.prev
print(result)Solution
Step 1: Understand the linked list connections
Nodes are linked as 1 <-> 2 <-> 3 with prev and next pointers set correctly.Step 2: Trace the while loop starting from third node
Starting at node with data 3, append 3, move to prev (2), append 2, move to prev (1), append 1, then prev is None, loop ends.Final Answer:
[3, 2, 1] -> Option AQuick Check:
Traverse backward from third node = [3, 2, 1] [OK]
- Confusing next and prev pointers during traversal
- Starting traversal from head instead of third
- Appending data in wrong order
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
head = Node(1)
new_node = Node(2)
# Insert new_node after head
new_node.next = head.next
head.next.prev = new_node
head.next = new_node
new_node.prev = headSolution
Step 1: Analyze head.next before insertion
Initially, head.next is None, so head.next.prev causes AttributeError.Step 2: Fix by checking if head.next exists before accessing prev
Adding a condition to check if head.next is not None before setting head.next.prev avoids error.Final Answer:
head.next.prev causes AttributeError; fix by checking if head.next is not None before accessing prev. -> Option CQuick Check:
Check for None before accessing prev pointer [OK]
- Ignoring None check causing runtime error
- Assigning pointers in wrong order
- Assuming head.next is always valid
Solution
Step 1: Set new_node pointers to neighbors
new_node.prev should point to node2 and new_node.next to node3 to link correctly.Step 2: Update neighbors to point to new_node
node2.next must point to new_node and node3.prev must point to new_node to complete insertion.Final Answer:
new_node.prev = node2; new_node.next = node3; node2.next = new_node; node3.prev = new_node -> Option AQuick Check:
Set new_node pointers then update neighbors [OK]
- Swapping prev and next pointers
- Updating neighbors before setting new_node pointers
- Forgetting to update one neighbor pointer
Ready to prove it?
Pass the test and this pattern moves unknown
