Insert at End of Doubly Linked List in DSA Python - Time & Space Complexity
We want to understand how the time needed to add a new item at the end of a doubly linked list changes as the list grows.
Specifically, how does the number of steps grow when the list gets longer?
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 insert_at_end(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
temp = self.head
while temp.next:
temp = temp.next
temp.next = new_node
new_node.prev = temp
This code adds a new node with given data at 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 in the list until the end is reached.
As the list gets longer, the time to reach the end grows roughly in direct proportion to the number of nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to reach the end |
| 100 | About 100 steps to reach the end |
| 1000 | About 1000 steps to reach the end |
Pattern observation: The steps increase linearly as the list size increases.
Time Complexity: O(n)
This means the time to insert at the end grows linearly with the number of nodes in the list.
[X] Wrong: "Inserting at the end is always fast and takes constant time."
[OK] Correct: Without a pointer to the last node, we must walk through the whole list, so time grows with list size.
Understanding this helps you explain how linked lists work and why keeping track of the last node can speed up insertions.
"What if we kept a pointer to the tail (last node) of the list? How would the time complexity change?"
Practice
Solution
Step 1: Understand the structure of a doubly linked list
A doubly linked list has nodes connected forward and backward, allowing traversal in both directions.Step 2: Purpose of insertion at the end
Inserting at the end means adding a new node after the current last node, updating pointers accordingly.Final Answer:
To add a new element after the last node -> Option BQuick Check:
Insert at end = Add after last node [OK]
- Confusing insertion at end with deletion
- Thinking insertion reverses the list
- Assuming insertion happens at the start
Solution
Step 1: Identify pointer updates for insertion at end
The new node's next pointer should be null because it will be the last node. Its prev pointer should point to the current tail.Step 2: Update the tail's next pointer and move tail
The current tail's next pointer should point to the new node, then update tail to the new node.Final Answer:
new_node.next = null; new_node.prev = tail; tail.next = new_node; tail = new_node -> Option CQuick Check:
New node next = null, prev = tail [OK]
- Setting new_node.next to head instead of null
- Not updating tail pointer after insertion
- Confusing prev and next pointers
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def insert_end(self, data):
new_node = Node(data)
if not self.head:
self.head = self.tail = new_node
else:
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_node
def print_list(self):
current = self.head
result = []
while current:
result.append(current.data)
current = current.next
print(' -> '.join(map(str, result)) + ' -> null')
dll = DoublyLinkedList()
dll.insert_end(10)
dll.insert_end(20)
dll.insert_end(30)
dll.print_list()Solution
Step 1: Insert nodes 10, 20, 30 at the end
Each insert_end call adds a new node at the tail, linking nodes in order: 10, then 20, then 30.Step 2: Print the list from head to tail
print_list traverses from head, collecting data: 10, 20, 30, then prints with arrows and 'null' at end.Final Answer:
10 -> 20 -> 30 -> null -> Option AQuick Check:
Insert end adds at tail, print from head [OK]
- Confusing print order as reversed
- Missing last node in output
- Printing null instead of 'null'
def insert_end(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_nodeSolution
Step 1: Check tail update when list is empty
When the list is empty, only head is set to new_node, but tail is not updated, so tail remains null.Step 2: Correct tail assignment
Tail must also be set to new_node when list is empty to keep tail pointer valid.Final Answer:
Missing update of tail when list is empty -> Option DQuick Check:
Empty list needs tail = new_node too [OK]
- Not updating tail on empty list
- Setting new_node.next incorrectly
- Confusing head and tail updates
Solution
Step 1: Insert 35 at the end of the list
The new node with value 35 is added after the current tail (25), updating pointers accordingly.Step 2: Traverse from head to tail and tail to head
From head: 5 -> 15 -> 25 -> 35 -> null; From tail: 35 -> 25 -> 15 -> 5 -> null.Final Answer:
5 -> 15 -> 25 -> 35 -> null and 35 -> 25 -> 15 -> 5 -> null -> Option AQuick Check:
Forward and backward traversal show correct order [OK]
- Mixing forward and backward traversal order
- Forgetting to update prev pointer of new node
- Assuming new node is added at head
Ready to prove it?
Pass the test and this pattern moves unknown
