Linked List
Overview
Linear data structure where elements are linked via references — not stored contiguously in memory.
| Feature | Array | Linked List |
|---|---|---|
| Access by index | O(1) | O(n) |
| Insert/Delete at head | O(n) | O(1) |
| Insert/Delete at tail | O(1) amortized | O(1) with tail ref |
| Memory overhead | Low | High (next pointer) |
| Random access | Yes | No |
Use over array when: O(1) insertions/deletions at both ends matter (e.g., implementing Queue, Deque). Avoid when: random access is needed — arrays are almost always faster in practice.
Singly Linked List
class Node:
def __init__(self, val):
self.val = val
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, val): # O(n) without tail ref
node = Node(val)
if not self.head:
self.head = node
return
curr = self.head
while curr.next:
curr = curr.next
curr.next = node
def prepend(self, val): # O(1)
node = Node(val)
node.next = self.head
self.head = node
def delete(self, val): # O(n)
if not self.head:
return
if self.head.val == val:
self.head = self.head.next
return
curr = self.head
while curr.next:
if curr.next.val == val:
curr.next = curr.next.next
return
curr = curr.next
def to_list(self):
result, curr = [], self.head
while curr:
result.append(curr.val)
curr = curr.next
return result
Doubly Linked List
class DNode:
def __init__(self, val):
self.val = val
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None
def append(self, val): # O(1) with tail ref
node = DNode(val)
if not self.tail:
self.head = self.tail = node
return
node.prev = self.tail
self.tail.next = node
self.tail = node
def pop(self): # O(1) remove from tail
if not self.tail:
return None
val = self.tail.val
self.tail = self.tail.prev
if self.tail:
self.tail.next = None
else:
self.head = None
return val
Key Techniques
# Reverse a linked list — O(n) time, O(1) space
def reverse(head):
prev, curr = None, head
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev
# Find middle (slow/fast pointers)
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# Detect cycle (Floyd's)
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False