Introduction
Python's list is a dynamic array, allowing O(1) random access, but O(n) for Insertion/Deletion-is in the middle.
A linked list, on the other hand, has O(1) at a known position Insertion/Deletion but random access is O(n).
In Python, there aren't many opportunities to implement linked lists directly, but for coding tests, understanding the concept and Problems that test the ability to manipulate pointers (references) are frequently given.
Pointer (Reference) Concept:
Node: [data | next] -> [data | next] -> [data | next] -> None
head tail
Each node has a reference to the next node. In Python, this is a simple object reference.
Learning Objectives
- Define the Node class and directly implement the Simple/Double/Circular linked list.
- Understand that the linked list has Insert/Delete O(1) insertion, O(n) search complexity, and compare it with a Python list.
- Can solve representative problems such as reversal, cycle detection (Floyd's), and central node search.
Key Concepts
1. Node Class
class Node:
def __init__(self, data):
self.data = data
self.next = None #Refer to the next node
2. Singly Linked List
head
|
[1] -> [2] -> [3] -> [4] -> None
| Operation | Time Complexity | Description |
|---|---|---|
| Insert at the front | O(1) | Only changes the head pointer |
| Insert at the end | O(n) or O(1) | O(1) if there is a tail pointer |
| Insert at arbitrary position | O(n) | Change pointer after search |
| Delete First | O(1) | move head |
| Delete at arbitrary position | O(n) | Change pointer after search |
| Search | O(n) | Sequential search |
| Index access | O(n) | Sequential search |
3. Doubly Linked List
head tail
| |
[1] <-> [2] <-> [3] <-> [4] <-> None
Each node has two pointers, prev and next.
When deleting at an arbitrary position, the previous node can be accessed in O(1) -> deletion O(1) (when node reference exists).
4. Circular Linked List
head
|
[1] -> [2] -> [3] -> [4]
^ |
+--------------------+
The last node points to the head. Circular structure.
5. Python list vs Linked List
| Operation | Python list | Linked list |
|---|---|---|
| Random access arr[i] | O(1) | O(n) |
| Insert at end | O(1) amortized | O(1) (if tail exists) |
| Insert at front | O(n) | O(1) |
| Insert in middle | O(n) | O(1) (if node is known) |
| Delete in middle | O(n) | O(1) (if node is known) |
| Search | O(n) | O(n) |
| Memory | Contiguous array (cache-friendly) | Non-contiguous (pointer overhead) |
Examples
| File | Content | Key Concept |
|---|---|---|
src/01_singly_linked_list.py |
Complete implementation of singly linked list | append, prepend, delete, search |
src/02_doubly_linked_list.py |
Doubly linked list | prev/next pointers |
src/03_circular_linked_list.py |
Circular Linked List | Cyclic Structure |
src/04_linked_list_problems.py |
Reverse, cycle detection, middle node | Floyd's Algorithm |
Viewing from a different perspective -- Train connection analogy
[Locomotive] -> [Carriage1] -> [Carriage2] -> [Carriage3] -> (End)
head node node tail
Add a carriage in the middle: Just need to change the connection of the previous carriage -> O(1)
Remove a carriage in the middle: Connect the previous carriage directly to the next carriage -> O(1)
Find the 3rd carriage: Must count in order from the front -> O(n)
Common Mistakes
Mistake 1: Missing None check -- NoneType error
#Incorrect example
def traverse(head):
node = head
while node:
print(node.data)
node = node.next #If the node is None, accessing .next will cause an error!
#Correct example
def traverse(head):
node = head
while node is not None:
print(node.data)
node = node.next
Mistake 2: Managing prev pointer on deletion
#Incorrect example -- does not update the previous node
def delete_node(head, target):
node = head
while node:
if node.data == target:
node = node.next #The previous node still points to a deleted node!
node = node.next
#Correct example
def delete_node(head, target):
if head.data == target:
return head.next
node = head
while node.next:
if node.next.data == target:
node.next = node.next.next #Skipping the previous node's next
return head
node = node.next
return head
Mistake 3: Pointer order when reversing
#Incorrect example -- if you change next first, you lose the original next
node.next = prev #You lose the original node.next!
prev = node
node = node.next # None!
#Correct example
next_node = node.next #1. Save first
node.next = prev #2. Reverse
prev = node #3. Move prev
node = next_node #4. Move node
Mistake 4: None check in cycle detection
#Incorrect example -- slow/fast can be None
while slow != fast:
slow = slow.next
fast = fast.next.next #If fast is None, AttributeError!
#Correct example
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True #There is a cycle
return False
Mistake 5: Updating both directions in doubly linked list
#Incorrect example -- only next is updated
new_node.next = current.next
current.next = new_node
#current.next.prev update omitted!
#Correct example
new_node.next = current.next
new_node.prev = current
if current.next:
current.next.prev = new_node
current.next = new_node
Summary
| Concept | Key Point |
|---|---|
| Singly Linked List | next pointer, Insert/Delete O(1) at known position |
| Doubly Linked List | prev+next, bidirectional O(1) |
| Circular Linked List | last node references head |
| Reverse | Use 3 pointers (prev, cur, next) |
| Cycle Detection | Floyd's Tortoise and Hare Algorithm |
| Middle Node | Fast/Slow pointer |
Try it yourself
- Track each operation by hand in
src/01_singly_linked_list.py. - Simulate Floyd's algorithm directly in
src/04_linked_list_problems.py. - Solve Baekjoon 1406 (Editor) problem using a doubly linked list.
- Solve the exercises in the assignment below.