4.1 Linked Lists & LRU Cache
Understand what a Node is, why Linked Lists exist compared to Arrays, how to rewire pointers without losing the list, and how to build an LRU Cache.
1. What Is a Linked List?
An Array stores all of its items side-by-side in one single block of memory. A Linked List does the exact opposite: its items can live anywhere in memory, connected like a treasure hunt where each box (called a Node) holds two things:
val: The value stored inside this node (e.g.,10).next: A pointer (memory address arrow) pointing to the next node in the chain, ornull(None) at the end.
In a Doubly Linked List, each node also holds a prev arrow pointing backward to the previous node.
| Operation | Dynamic Array (list / vector) | Singly Linked List (head -> next) | Doubly Linked List (prev <-> next) |
|---|---|---|---|
Read k-th item (arr[k]) | O(1) (Instant math) | O(N) (Must walk k arrows) | O(N) (Must walk k arrows) |
Insert / Delete at Front (head) | O(N) (Shifts all items) | O(1) (Rewire 1 pointer) | O(1) (Rewire 2 pointers) |
Delete a Given Node curr in Middle | O(N) (Shifts items left) | O(N) (Must find node before curr) | O(1) (curr.prev.next = curr.next) |
| Extra Memory Overhead per Item | 0 extra bytes | 1 pointer (8 bytes) | 2 pointers (16 bytes — used in LRU Cache!) |
2. Why Do We Need Linked Lists & How Do They Work?
The Pain With Arrays (O(N) Middle Insert/Delete)
In an Array, inserting or deleting an item at the front or middle forces every subsequent item to shift over in memory (O(N)).
Why Linked Lists Win at Splicing (O(1))
If you already have a pointer to a node curr in a Linked List:
- Inserting a new node right after
currtakesO(1)time: Just pointnew_node.next = curr.next, and thencurr.next = new_node. Zero items shift in memory! - Deleting the node after
currtakesO(1)time: Just bypass it withcurr.next = curr.next.next. - The Trade-Off: You lose
O(1)index lookup (arr[5]). To reach the 5th node in a Linked List, you must start atheadand follow.next5 times (O(N)).
3. Basic Operations & Walkthroughs (All 4 Languages)
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 1. Build 10 -> 20 -> 30
head = ListNode(10, ListNode(20, ListNode(30)))
# 2. Traverse from head to null -> O(N)
curr = head
while curr:
# read curr.val
curr = curr.next
# 3. Insert 15 right after head (10) -> O(1)
new_node = ListNode(15)
new_node.next = head.next
head.next = new_node # 10 -> 15 -> 20 -> 30
# 4. Delete the node after head (removes 15) -> O(1)
head.next = head.next.next # back to 10 -> 20 -> 304. The 4 Essential Interview Patterns
Pattern 1: In-Place Pointer Reversal (prev, curr, nxt)
To reverse a linked list in O(1) memory, walk curr forward while flipping each .next arrow backward toward prev. Golden Rule: Always save nxt = curr.next before you overwrite curr.next = prev, or you lose the rest of the list!
Interactive Step-by-Step Trace: In-Place Linked List Reversal (3 Pointers)
Watch how prev, curr, and next_node flip each arrow one at a time:
If you overwrite curr.next first without saving curr.next, node 20 and 30 are lost forever.
Pattern 2: Dummy Sentinel Node (dummy = ListNode(0, head))
Whenever the head of the list might change (merging lists, deleting the 1st node), create a dummy node before head. Attach everything to dummy, and return dummy.next!
Pattern 3: Fast & Slow Pointers (Floyd's Cycle & Middle Detection)
Move slow by 1 step and fast by 2 steps:
- When
fastreachesnull,slowis at the exact middle of the list. - If there is a cycle,
fastlapsslowand they collide (slow == fast) inO(N)time andO(1)memory.
Pattern 4: LRU Cache (Hash Map + Doubly Linked List)
To get O(1) lookup AND O(1) recency updates:
- Store
key -> Nodein a Hash Map for instantO(1)lookup. - Connect all nodes in a Doubly Linked List between dummy
HEAD (LRU)andTAIL (MRU)so any accessed node can be unlinked from the middle and moved toTAILinO(1)time!
Use 3 pointers whenever reversing a full list or sublist (like the second half of a Palindrome or Reorder List) in O(1) memory.
# 1. Find Middle (slow stops at middle):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 2. Reverse from slow onward:
prev, curr = None, slow
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt| Operation / Pattern | Time | Space | Notes |
|---|---|---|---|
| Access k-th Element | O(N) | O(1) | Must walk k pointers from head |
| Insert / Delete After Known Node | O(1) | O(1) | Rewires 2 pointers; no shifting |
| In-Place Reversal / Cycle Detection | O(N) | O(1) | Uses 2 or 3 pointer variables |
| LRU Cache (get & put) | O(1) | O(capacity) | Hash Map + Doubly Linked List |
5. Practice On Your Own (Curated Submodule Problems)
| Status | Problem | Difficulty | Complexity | Hint? | Links & Notes |
|---|---|---|---|---|---|
Reverse Linked List In-Place Pointer Reversal | Easy | T: O(N) S: O(1) | |||
Merge Two Sorted Lists Dummy Sentinel Node | Easy | T: O(M + N) S: O(1) | |||
Linked List Cycle Fast & Slow Pointers (Cycle & Middle) | Easy | T: O(N) S: O(1) | |||
Remove Nth Node From End of List Dummy Sentinel Node | Medium | T: O(N) S: O(1) | |||
LRU Cache Hash Map + Doubly Linked List (LRU Cache) | Medium | T: O(1) S: O(capacity) |