Linked List
Scope — Pointer surgery on singly and doubly linked lists — reversal, merging, reordering, dummy-head technique, and cycle handling. See also: linked_list_examples.md — the worked solutions these templates are for; 2_pointers_linkedlist.md — the fast/slow pointer specialisation; design.md — LRU and other list+map designs; heap.md — k-way list merging; recursion.md — recursive list rewriting.
LeetCode Problem Lists
Time Complexity
| Data structure | Search | Insert | Delete | Min/Max |
|---|---|---|---|---|
| Linked List | O(n) | O(1) | O(1) | O(n) |
Insert / Delete are O(1) given the target node (e.g. head, or a node you already hold); locating that node first is O(n).
0) Concept
-
Use “pseudo head node” 虛擬頭節點
-
When delete node from linked list, we need to be at “previous” node, then can delete NEXT node
- so, need to be at
curnode, than cancur.nextnode
python# python # https://youtu.be/Y4oQJklHxVo?t=965 cur.next = cur.next.next - so, need to be at
# python
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
// java
// Single Linkedlist
public class ListNode{
// attr
public int val;
public ListNode next;
// constructor
public ListNode(){
}
public ListNode(int val){
this.val = val;
}
ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
}
// init a ListNode
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
// motify node's value
node1.val = 0;
// connect nodes
node1.next = node2;
node2.next = node3;
// java
// Double linked list
// LC 146
public class Node {
int key;
int val;
Node prev;
Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
this.prev = null;
this.next = null;
}
}
0-1) Types
- Linked list
- Cycle linked list
- Bi-direction linked list
- Double Linked list
- LC 146
- Others
- LC 138 :
pythondic = dict() m = n = head dic[m] = Node(m.val)- LC 208 :
- trie
pythonself.children = defaultdict(Node) - problem types
- reverse
- reverse linked list
- LC 206
- reverse linked list within start, end point
- LC 92, LC 25
- reverse part of linked list
- reverse k set of linked list
- reverse linked list
- merge
- merge 2 linked list
- check
- check cyclic linked list
- check beginning of cyclic linked list
- remove N th node
- Remove Nth Node From End of List - LC 19
- combinations
- combinations of above cases
- reverse
0-2) Pattern
Dummy Head Technique
Definition: Create a dummy/pseudo head node that points to the actual head, making it easier to handle edge cases and node removal operations.
When to Use:
- Removing nodes from the beginning of the list
- When the head node might be modified
- Simplifying edge case handling
- Operations that need to track the previous node
Time Complexity: O(n) - same as without dummy head Space Complexity: O(1) - only one extra node
Template Pattern:
def linked_list_operation(head):
# Create dummy head
dummy = ListNode(0)
dummy.next = head
# Use prev to track previous node
prev = dummy
curr = head
while curr:
# Perform operations
if condition:
# Remove current node
prev.next = curr.next
else:
prev = curr
curr = curr.next
# Return new head (dummy.next)
return dummy.next
Advantages:
- Eliminates need for special handling of head node
- Simplifies code logic
- Reduces edge case bugs
- Consistent prev pointer throughout traversal
Why Dummy Node? Visual Comparison (LC 19)
Problem: Remove the n-th node from the end of
[1, 2, 3, 4, 5].
Case A — Normal removal: n = 2 (remove node 4)
Without dummy — works fine here:
fast = slow = head = [1]
Step 1: move fast n=2 steps ahead
[1] -> [2] -> [3] -> [4] -> [5]
^slow ^fast
Step 2: move both until fast.next is None
[1] -> [2] -> [3] -> [4] -> [5]
^slow ^fast
Step 3: slow.next = slow.next.next → removes [4]
[1] -> [2] -> [3] -> [5] ✓
With dummy — also works, same logic:
fast = slow = dummy[0]
Step 1: move fast n+1=3 steps ahead
[0] -> [1] -> [2] -> [3] -> [4] -> [5]
^slow ^fast
Step 2: move both until fast is None
[0] -> [1] -> [2] -> [3] -> [4] -> [5]
^slow ^fast → None (stop)
Step 3: slow.next = slow.next.next → removes [4]
[0] -> [1] -> [2] -> [3] -> [5] → return dummy.next = [1] ✓
Case B — Edge case: n = 5 (remove the head node 1)
Without dummy — BREAKS, needs special-case code:
fast = slow = head = [1]
Step 1: move fast n=5 steps
fast: 1 -> 2 -> 3 -> 4 -> 5 -> None
[1] -> [2] -> [3] -> [4] -> [5] -> None
^slow ^fast (None!)
Step 2: while fast.next → fast is None, loop NEVER runs
slow is still at [1] (the head itself!)
Step 3: slow.next = slow.next.next
→ This removes [2], NOT the head — WRONG ❌
Must add a special case:
if not fast:
return head.next # ← extra branch needed
With dummy — works uniformly, NO special case:
fast = slow = dummy[0]
Step 1: move fast n+1=6 steps ahead
fast: dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> None
[0] -> [1] -> [2] -> [3] -> [4] -> [5] -> None
^slow ^fast (None)
Step 2: while fast → fast is None, loop NEVER runs
slow stays at dummy[0] ← one node BEFORE the head
Step 3: slow.next = slow.next.next
dummy.next = [2] → head [1] is removed ✓
Return dummy.next = [2] -> [3] -> [4] -> [5] ✓ No special case!
Summary: Why dummy wins
| Without Dummy | With Dummy | |
|---|---|---|
| Normal removal | ✓ Works | ✓ Works |
| Remove head (n = len) | ❌ Needs if not fast: return head.next |
✓ Works uniformly |
| Code branches | Extra conditional | None |
slow start position |
head (can’t reach before head) |
dummy (one step before head) |
Key insight: the dummy node gives slow a “standing position” one node before the head, so it can reconnect across any node — including the head itself — without special handling.
# LC 19 — with dummy (handles all cases cleanly)
def removeNthFromEnd(self, head, n):
dummy = ListNode(0)
dummy.next = head
fast = slow = dummy
for _ in range(n + 1): # fast moves n+1 steps
fast = fast.next
while fast: # move both until fast is None
fast = fast.next
slow = slow.next
slow.next = slow.next.next # remove the target node
return dummy.next
Dummy Head — Other Applications
Two more problems where the dummy is what removes the special case. The rest of the family is worked where it belongs, not re-pasted here:
| LC | Problem | Worked in |
|---|---|---|
| 19 | Remove Nth Node From End | the visual walkthrough above, and linked_list_examples.md for both Java forms |
| 21 | Merge Two Sorted Lists | linked_list_examples.md |
| 2 | Add Two Numbers | 1-1-7) below |
| 203 | Remove Linked List Elements | Remove Elements by Value Pattern below |
Remove duplicates from a sorted list — LC 83: the dummy holds the last kept node, so a run of equal values collapses without ever special-casing a duplicated head.
def deleteDuplicates(self, head):
dummy = ListNode(0)
dummy.next = head
prev = dummy
while head and head.next:
if head.val == head.next.val:
# Skip all duplicates
val = head.val
while head and head.val == val:
head = head.next
prev.next = head
else:
prev = head
head = head.next
return dummy.next
Partition List — LC 86: two dummies. Build the < x chain and the >= x chain
independently, then join them — no in-place surgery, and stability falls out for free.
def partition(self, head, x):
before_dummy = ListNode(0)
after_dummy = ListNode(0)
before = before_dummy
after = after_dummy
while head:
if head.val < x:
before.next = head
before = before.next
else:
after.next = head
after = after.next
head = head.next
# Connect the two parts
after.next = None
before.next = after_dummy.next
return before_dummy.next
Key Benefits of Dummy Head:
| Aspect | Without Dummy | With Dummy |
|---|---|---|
| Edge Cases | Complex head handling | Unified approach |
| Code Length | More conditional logic | Cleaner, shorter |
| Bug Probability | Higher (edge cases) | Lower (consistent) |
| Readability | Harder to follow | More intuitive |
Related Problems:
- LC 19: Remove Nth Node From End of List
- LC 21: Merge Two Sorted Lists
- LC 83: Remove Duplicates from Sorted List
- LC 86: Partition List
- LC 203: Remove Linked List Elements
- LC 328: Odd Even Linked List
Remove Elements by Value Pattern
Definition: Remove all nodes from a linked list that match a specific value. Uses dummy head and a “look ahead” technique where the current pointer examines curr.next rather than curr itself.
Core Concept:
- Key Insight: When we find a node to remove, we ONLY update the pointer connection (
curr.next = curr.next.next), but thecurrpointer itself does NOT move forward - This allows handling consecutive matching nodes (e.g.,
[6,6,6,3]with val=6) - Only move
currforward whencurr.next.val != val
When to Use:
- Removing nodes by value from anywhere in the list
- Handling cases where head node(s) might need removal
- Removing consecutive duplicate values
Time Complexity: O(n) Space Complexity: O(1)
Template Pattern:
// Java
public ListNode removeElements(ListNode head, int val) {
// 1. Create dummy node pointing to head
ListNode dummy = new ListNode(0);
dummy.next = head;
// 2. Use curr pointer (starts at dummy, looks ahead)
ListNode curr = dummy;
// 3. Look ahead at NEXT node
while (curr.next != null) {
if (curr.next.val == val) {
// Found match - skip the next node
// NOTE: curr does NOT move!
curr.next = curr.next.next;
} else {
// No match - move pointer forward
curr = curr.next;
}
}
// 4. Return actual head
return dummy.next;
}
# Python
def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
curr = dummy
while curr.next:
if curr.next.val == val:
curr.next = curr.next.next # skip, don't move curr
else:
curr = curr.next # move forward
return dummy.next
Dry Run Example ([6,6,6,3], val=6):
Initial: dummy -> 6 -> 6 -> 6 -> 3, curr at dummy
Step 1: curr.next.val = 6 (match!)
Action: curr.next = curr.next.next
Result: dummy -> 6 -> 6 -> 3 (curr STAYS at dummy)
Step 2: curr.next.val = 6 (match!)
Action: curr.next = curr.next.next
Result: dummy -> 6 -> 3 (curr STAYS at dummy)
Step 3: curr.next.val = 6 (match!)
Action: curr.next = curr.next.next
Result: dummy -> 3 (curr STAYS at dummy)
Step 4: curr.next.val = 3 (no match)
Action: curr = curr.next
Result: curr moves to node 3
Step 5: curr.next = null, exit loop
Return: dummy.next = [3]
Why This Works for Consecutive Matches:
| Scenario | Without “stay in place” | With “stay in place” |
|---|---|---|
[6,6,3] val=6 |
Would skip second 6 | Catches all 6s |
| Head removal | Needs special case | Handled uniformly |
Similar LC Problems:
- LC 203: Remove Linked List Elements (exact pattern)
- LC 83: Remove Duplicates from Sorted List (similar, compare adjacent)
- LC 82: Remove Duplicates from Sorted List II (remove all duplicates)
- LC 237: Delete Node in a Linked List (different - no access to prev)
- LC 1474: Delete N Nodes After M Nodes (pattern variation)
- LC 2487: Remove Nodes From Linked List (stack-based variation)
Doubly Linked List + HashMap (LRU Cache Pattern) Priority 5 of 5 — Must know — expect it in almost every loop
Core Idea: Combine a HashMap for O(1) key lookup with a doubly linked list for O(1) ordered eviction. Most-recently-used nodes sit near the tail; least-recently-used sits near the head. Sentinel dummy head/tail nodes eliminate all edge-case pointer checks.
Layout:
head(dummy) <-> [LRU] <-> ... <-> [MRU] <-> tail(dummy)
When to Use:
- Need O(1) get + O(1) put with ordered eviction (LRU/MFU)
- Any problem requiring a ordered access-tracked collection
Time Complexity: O(1) get and put
Space Complexity: O(capacity)
Key Helper Operations:
_remove(node)— splice a node out of the list in O(1)_insert(node)— insert a node just before tail (MRU position) in O(1)
Template Pattern:
# python
# LC 146 - LRU Cache
class Node:
def __init__(self, key, val):
self.key = key
self.val = val
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {} # key -> Node
# sentinel boundaries: head <-> ... <-> tail
self.head = Node(0, 0) # LRU side
self.tail = Node(0, 0) # MRU side
self.head.next = self.tail
self.tail.prev = self.head
def _remove(self, node):
prev = node.prev
nxt = node.next
prev.next = nxt
nxt.prev = prev
def _insert(self, node): # insert just before tail (MRU)
prev = self.tail.prev
prev.next = node
node.prev = prev
node.next = self.tail
self.tail.prev = node
def get(self, key):
if key not in self.cache:
return -1
node = self.cache[key]
self._remove(node)
self._insert(node) # move to MRU
return node.val
def put(self, key, value):
if key in self.cache:
node = self.cache[key]
node.val = value
self._remove(node)
self._insert(node) # refresh to MRU
return
if len(self.cache) == self.capacity:
lru = self.head.next # evict LRU (closest to head)
self._remove(lru)
del self.cache[lru.key]
node = Node(key, value)
self.cache[key] = node
self._insert(node)
Visual Trace (capacity=2):
put(1,1): head <-> [1] <-> tail
put(2,2): head <-> [1] <-> [2] <-> tail
get(1): head <-> [2] <-> [1] <-> tail ← 1 moved to MRU
put(3,3): evict head.next=[2]
head <-> [1] <-> [3] <-> tail
Why sentinel nodes?
_removeand_insertalways have valid.prev/.nextneighbors- No
if node.prev is Noneorif node.next is Noneguards needed - Works uniformly for head removal, tail removal, and middle removal
Similar LC Problems:
| # | Problem | Key Difference |
|---|---|---|
| 146 | LRU Cache | Classic pattern — evict least recently used |
| 460 | LFU Cache | Two-level structure: frequency map + per-freq doubly linked list |
| 432 | All O(1) Data Structure | Doubly linked list of count buckets |
| 1472 | Design Browser History | Doubly linked list, truncate forward on visit |
| 641 | Design Circular Deque | Doubly linked list with fixed capacity, both ends |
| 716 | Max Stack | Stack + doubly linked list + TreeMap for O(log n) popMax |
Reverse K Nodes Helper Pattern Priority 5 of 5 — Must know — expect it in almost every loop
Core Idea: Almost every “reverse a segment” problem (LC 92, LC 25, LC 24, LC 206) is the same primitive — reverse k nodes starting from a head, then reconnect. Factor that primitive into a single reusable helper so the outer solution only worries about locating the segment and stitching the ends back together.
The helper reverses k nodes and returns three handles you need to reconnect cleanly:
# python — reusable helper: reverse k nodes starting at `head`
# time = O(k), space = O(1)
def reverse_helper(self, head, k):
prev = None
curr = head
while curr and k > 0:
nxt = curr.next # 1) cache next
curr.next = prev # 2) reverse the link
prev = curr # 3) advance prev
curr = nxt # 4) advance curr
k -= 1
# prev = new head of reversed list (was the k-th node)
# head = new tail (original head, now points forward to `curr`)
# curr = first node AFTER the reversed segment
return prev, head, curr
Why return 3 things? After reversing an inner segment you must re-wire both boundaries:
| Returned | What it is | Used to reconnect |
|---|---|---|
prev (new_head) |
new head of the reversed chunk | prev_of_segment.next = new_head |
head (new_tail) |
new tail (the original first node) | new_tail.next = next_node |
curr (next_node) |
first node after the segment | the tail must point here |
When to Use:
- Reverse a sub-range
[left, right](LC 92) → reverseright - left + 1nodes - Reverse every k-group (LC 25) → call helper in a loop until fewer than
kremain - Reverse whole list (LC 206) → call helper once with
k = length(ork = ∞)
Template — apply helper to LC 92 (Reverse Linked List II):
# python
# LC 92 - reverse nodes from position `left` to `right`
# time = O(n), space = O(1)
class Solution(object):
def reverseBetween(self, head, left, right):
# edge case
if not head or left == right:
return head
dummy = ListNode(0)
dummy.next = head
# 1) walk `prev` to the node BEFORE position `left`
prev = dummy
for _ in range(left - 1):
prev = prev.next
# 2) `start` = first node of the segment to reverse
start = prev.next
# 3) reverse (right - left + 1) nodes via the helper
new_head, new_tail, next_node = self.reverse_helper(
start, right - left + 1
)
# 4) reconnect both boundaries
prev.next = new_head # front: prev -> new head of reversed chunk
new_tail.next = next_node # back: old head (now tail) -> rest of list
return dummy.next
def reverse_helper(self, head, k):
prev = None
curr = head
while curr and k > 0:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
k -= 1
return prev, head, curr
Visualization ([1,2,3,4,5], left=2, right=4 → reverse 3 nodes 2,3,4):
dummy -> 1 -> 2 -> 3 -> 4 -> 5
└──── reverse these 3 ────┘
Step 1) walk prev (left-1 = 1 step) to node before segment
dummy -> 1 -> 2 -> 3 -> 4 -> 5
^prev ^start
(start = prev.next = node 2)
Step 2) reverse_helper(start=2, k=3)
-- reverses links of 2,3,4 in isolation --
before: 2 -> 3 -> 4 -> 5
after: 2 <- 3 <- 4 5
| |
new_tail new_head
returns:
new_head = 4 (was k-th node, now front of chunk)
new_tail = 2 (was `start`, now points nowhere yet)
next_node = 5 (first node after the reversed part)
Step 3) reconnect boundaries
(C1) prev.next = new_head
node1.next -> 4
(C2) new_tail.next = next_node
node2.next -> 5
Final:
dummy -> 1 -> 4 -> 3 -> 2 -> 5
└── reversed ──┘
return dummy.next => [1, 4, 3, 2, 5] ✓
The 3 boundary handles, visually:
prev new_head → ... → new_tail next_node
| | | |
... -> 1 4 -> 3 -> 2 (dangling) 5 -> ...
|_____________| |_______________|
(C1) prev.next = new_head (C2) new_tail.next = next_node
Reusing the helper for LC 25 (Reverse Nodes in k-Group):
# python
# LC 25 - reverse every k nodes; leave the tail (< k) as-is
# time = O(n), space = O(1)
class Solution(object):
def reverseKGroup(self, head, k):
# count if >= k nodes remain
def has_k(node, k):
cnt = 0
while node and cnt < k:
node = node.next
cnt += 1
return cnt == k
dummy = ListNode(0)
dummy.next = head
prev = dummy # node before current group
while has_k(prev.next, k):
start = prev.next
new_head, new_tail, next_node = self.reverse_helper(start, k)
prev.next = new_head # front of group
new_tail.next = next_node # tail of group -> rest
prev = new_tail # move `prev` to end of this group
return dummy.next
def reverse_helper(self, head, k):
prev = None
curr = head
while curr and k > 0:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
k -= 1
return prev, head, curr
Key insight: the same
reverse_helperpowers LC 206 / 92 / 25. Only the surrounding logic differs — 206 calls it once, 92 locates one segment then calls it once, 25 loops and calls it per group. Master the 3-handle return (new_head, new_tail, next_node) and all three collapse into “locate → reverse → reconnect”.
Similar LC Problems:
| # | Problem | How the helper applies |
|---|---|---|
| 206 | Reverse Linked List | One call, k = length — only new_head matters |
| 92 | Reverse Linked List II | Locate segment, one call with k = right - left + 1, reconnect both ends |
| 25 | Reverse Nodes in k-Group | Loop the helper per group; skip the final < k tail |
| 24 | Swap Nodes in Pairs | Special case k = 2 per group |
| 61 | Rotate List | Different op, but same “locate boundary + re-stitch” discipline |
1) General form
// java
// single Linklist
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
# python
class Node:
"""
# constructor
# A single node of a singly linked list
"""
def __init__(self, data=None, next=None):
self.data = data
self.next = next
class LinkedList:
"""
# A Linked List class with a single head node
"""
def __init__(self):
self.head = Node()
def get_length(self):
"""
# get list length method for the linked list
i.e.
before : 1 -> 2 -> 3
after : 3
"""
current = self.head
length = 0
while current:
current = current.next
length += 1
return length
def get_tail(self):
"""
# get list tail method for the linked list
i.e.
before : a -> b -> c
after : c
"""
current = self.head
while current:
current = current.next
return current
def print(self):
"""
# print method for the linked list
i.e.
before : 1 -> 2 -> 3
after : 1 2 3
"""
current = self.head
while current:
print (current.data)
current = current.next
def append(self, data):
"""
# append method that append a new item at the end of the linkedlist
i.e.
before : 1 -> 2 -> 3
after : 1 -> 2 -> 3 -> 4
"""
newNode = Node(data)
if self.head:
current = self.head
while current.next:
current = current.next
current.next = newNode
else:
self.head = newNode
def prepend(self, data):
"""
# append method that append a new item at the head of the linkedlist
i.e.
before : 1 -> 2 -> 3
after : 0 -> 1 -> 2 -> 3
"""
newNode = Node(data)
if self.head:
current = self.head
self.head = newNode
newNode.next = current
current = current.next
else:
self.head = newNode
def insert(self, idx, data):
"""
# append method that append a new item within the linkedlist
i.e.
before : 1 -> 2 -> 3
insert(1, 2.5)
after : 1 -> 2 -> 2.5 -> 3
before : 1 -> 2 -> 3
insert(0, 0)
after : 0 -> 1 -> 2 -> 3
before : 1 -> 2 -> 3
insert(2, 4)
after : 1 -> 2 -> 3 -> 4
"""
current = self.head
ll_length = self.get_length()
if idx < 0 or idx > self.get_length():
print ("idx out of linkedlist range, idx : {}".format(idx))
return
elif idx == 0:
self.prepend(data)
elif idx == ll_length:
self.append(data)
else:
newNode = Node(data)
current = self.head
cur_idx = 0
while cur_idx < idx-1:
current = current.next
cur_idx += 1
newNode.next = current.next
current.next = newNode
def remove(self, idx):
"""
# remove method for the linked list
i.e.
before : 1 -> 2 -> 3
remove(1)
after : 1 -> 3
before : 1 -> 2 -> 3
remove(2)
after : 1 -> 2
before : 1 -> 2 -> 3
remove(0)
after : 2 -> 3
"""
if idx < 0 or idx > self.get_length():
print ("idx out of linkedlist range, idx : {}".format(idx))
return
elif idx == 0:
current = self.head
self.head = current.next
elif idx == self.get_length():
current = self.head
cur_idx = 0
while cur_idx < idx -1:
current = current.next
cur_idx += 1
current.next = None
else:
current = self.head
cur_idx = 0
while cur_idx < idx - 1:
current = current.next
cur_idx += 1
next_ = current.next.next
current.next = next_
current = next_
def reverse(self):
"""
https://www.youtube.com/watch?v=D7y_hoT_YZI
# reverse method for the linked list
# https://www.geeksforgeeks.org/python-program-for-reverse-a-linked-list/
i.e.
before : 1 -> 2 -> 3
after : 3 -> 2 -> 1
"""
prev = None
current = self.head
while(current is not None):
next_ = current.next
current.next = prev
prev = current
current = next_
self.head = prev
1-1) Basic OP
1-1-1) Reverse linked list (iteration) — LC 206
# python
#-------------------------
# iteration
#-------------------------
# LC 206
# V0
# IDEA : Linkedlist basics
# https://www.youtube.com/watch?v=D7y_hoT_YZI
# STEPS)
# -> STEP 1) cache "next"
# -> STEP 2) point head.next to prev
# -> STEP 3) move prev to head
# -> STEP 4) move head to "next"
class Solution(object):
def reverseList(self, head):
# edge case
if not head:
return
prev = None
while head:
# cache "next"
tmp = head.next
# point head.next to prev
head.next = prev
# move prev to head (for next iteration)
prev = head
# move head to "next" (for next iteration)
head = tmp
# NOTE!!! we return prev
return prev
// java
//---------------------------
// iteration
//---------------------------
// LC 206
// V0
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode _prev = null;
while (head != null) {
/**
* NOTE !!!!
*
* 4 operations
*
* step 1) cache next
* step 2) point cur to prev
* step 3) move prev to cur
* step 4) move cur to next
*
*/
ListNode _next = head.next;
head.next = _prev;
_prev = head;
head = _next;
}
// NOTE!!! we return _prev here, since it's now "new head"
return _prev;
}
1-1-2) Reverse linked list (recursion) — LC 206
// java
//---------------------------
// recursion
//---------------------------
// LC 206
// algorithm book (labu) p.290
// IDEA: recurse to the tail, then let each frame flip the link BEHIND it on the way back.
// `newHead` is the original last node and is passed up unchanged.
// time = O(n), space = O(n) (call stack)
ListNode reverseList(ListNode head) {
// base case: empty list, or already at the last node
if (head == null || head.next == null) {
return head;
}
// reverse everything after `head`; newHead is the tail of the ORIGINAL list
ListNode newHead = reverseList(head.next);
/** NOTE !!!
*
* head.next is the node that now sits BEHIND head in the reversed list,
* so pointing it back at head is the whole flip.
*/
head.next.next = head;
// cut the old forward link, or the two nodes form a 2-cycle
head.next = null;
return newHead;
}
1-1-3) Reverse nodes in [a,b] linked list (iteration) — LC 92
// java
//---------------------------
// iteration
//---------------------------
// algorithm book (labu) p.298
ListNode reverse(ListNode a, Listnode b){
ListNode pre, cur, nxt;
pre = null;
cur = a;
nxt = a;
/** THE ONLY DIFFERENCE (reverse nodes VS reverse nodes in [a,b]) */
while (cur != b){
nxt = cur.next;
// reverse on each node
cur.next = pre;
// update pointer
pre = cur;
cur = nxt;
}
// return reversed nodes
return pre;
}
1-1-4) Reverse nodes in k group linked list (iteration) — LC 25
// java
//---------------------------
// iteration
//---------------------------
// LC 25
// algorithm book (labu) p.298
/** NOTE !!! `reverse(a, b)` is exactly the primitive from 1-1-3) above — reverse the
* half-open interval [a, b) and return its new head. Reproduced there, not here. */
ListNode reverseKGroup(ListNode head, int k){
if (head == null) return null;
// inverval [a,b] has k to-reverse elements
ListNode a, b;
a = b = head;
for (int i = 0; i < k; i++){
// not enough elements (amount < k), no need to reverse -> base case
if (b == null) return head;
b = b.next;
}
// reverse k elements
ListNode newHead = reverse(a,b);
// reverse remaining nodes, and connect with head
a.next = reverseKGroup(b,k);
return newHead;
}
# LC 025
class Solution:
def reverseKGroup(self, head, k):
# help func
# check if # of sub nodes still > k
def check(head, k):
ans = 0
while head:
ans += 1
if ans >= k:
return True
head = head.next
return False
# edge case
if not head:
return
d = dummy = ListNode(None)
pre = None
preHead = curHead = head
while check(curHead, k):
for _ in range(k):
# reverse linked list
tmp = curHead.next
curHead.next = pre
pre = curHead
curHead = tmp
# reverse linked list
# ???
dummy.next = pre
dummy = preHead
preHead.next = curHead
preHead = curHead
return d.next
1-1-5) Reverse first N linked list (recursion)
//---------------------------
// recursion
//---------------------------
// java
// algorithm book (labu) p.293
// "postorder" node
ListNode successor = null;
// reverse first N node (from head), and return new head
ListNode reverseN(ListNode head, int n){
if (n == 1){
// record n + 1 nodes, will be used in following steps
successor = head.next;
return head;
}
// set head.next as start point, return first n - 1 nodes
ListNode last = reverseN(head.next, n-1);
head.next.next = head;
// connect reversed head node and following nodes
head.next = successor;
return last;
}
1-1-6) Reverse middle N nodes in linked list (start, end as interval) (recursion) — LC 92
// java
//---------------------------
// recursion
//---------------------------
// algorithm book (labu) p.293
/** NOTE !!! `reverseN` is the primitive from 1-1-5) above, unchanged. Only the
* `reverseBetween` wrapper below is new: walk m down to 1, then reverse the first n. */
// reverse nodes in index = m to index = n
ListNode reverseBetween(ListNode head, int m, int n){
// base case
if (m == 1){
return reverseN(head, n);
}
// for head.next, the op is reverse interval : [m-1, n-1]
// will trigger base case when when meet reverse start point
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
}
1-1-7) add 2 linked list — LC 2
# LC 002
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
NOTE :
1. we init linkedlist via ListNode()
2. we NEED make extra head refer same linkedlist, since we need to return beginning of linkedlist of this func, while res will meet "tail" at the end of while loop
"""
head = res = ListNode()
plus = 0
tmp = 0
while l1 or l2:
tmp += plus
plus = 0
if l1:
tmp += l1.val
l1 = l1.next
if l2:
tmp += l2.val
l2 = l2.next
if tmp > 9:
tmp -= 10
plus = 1
res.next = ListNode(tmp)
res = res.next
tmp = 0
### NOTE : need to deal with case : l1, l2 are completed, but still "remaining" plus
if plus != 0:
res.next = ListNode(plus)
res = res.next
#print ("res = " + str(res))
#print ("head = " + str(head))
return head.next
# LC 445 Add Two Numbers II
# V0
# IDEA : string + linked list
# DEMO
# input :
# [7,2,4,3]
# [5,6,4]
# intermedia output :
# l1_num = 7243
# l2_num = 564
class Solution:
def addTwoNumbers(self, l1, l2):
if not l1 and not l2:
return None
l1_num = 0
while l1:
l1_num = l1_num * 10 + l1.val
l1 = l1.next
l2_num = 0
while l2:
l2_num = l2_num * 10 + l2.val
l2 = l2.next
print ("l1_num = " + str(l1_num))
print ("l2_num = " + str(l2_num))
### NOTE : trick here :
# -> get int format of 2 linked list first (l1, l2)
# -> then sum them (l1_num + l2_num)
lsum = l1_num + l2_num
head = ListNode(None)
cur = head
### NOTE : go thrpigh the linked list int sum, append each digit to ListNode and return it
for istr in str(lsum):
cur.next = ListNode(int(istr))
cur = cur.next
# NOTE : need to return head (but not cur, since cur already meet the end of ListNode)
return head.next
1-1-8) Find linked list middle point — LC 876
// algorithm book p. 286
// java
Listnode slow, fast;
slow = fast = head;
while (fast && fast.next){
fast = fast.next.next;
slow = slow.next;
}
// slow pointer will be linked list middle point
// if element count in linked list is odd (TO VERIFY)
if (fast != null){
slow = slow.next;
}
# LC 876 Middle of the Linked List
# V0
# IDEA : fast, slow pointers + linkedlist
class Solution(object):
def middleNode(self, head):
# edge case
if not head:
return
s = f = head
while f and f.next:
# if not f:
# break
f = f.next.next
s = s.next
return s
2) Pattern Selection
Linked-list problems are rarely about lists. They are about which handle you have to be holding when the surgery happens — and every technique on this sheet exists to make sure you are holding it. Pick by what the answer needs, not by the problem’s title.
| If the problem asks you to… | Reach for | Because | Written out at |
|---|---|---|---|
| delete or insert anywhere, head included | dummy head | it gives prev a standing position one node before the head, so “remove the head” stops being a special case |
Dummy Head Technique |
| remove every node matching a value | dummy + look-ahead on curr.next |
you have to be able to stay put after a deletion, or a run like [6,6,6] loses one |
Remove Elements by Value Pattern |
| reverse the whole list | the 3-step loop: cache next → flip → advance | O(1) space; the recursive form costs a frame per node for the same answer | 1-1-1) |
reverse a segment — [left, right], every k, or pairs |
the reverse-k helper, returning 3 handles | LC 92 / 25 / 24 differ only in where the segment is, never in how it reverses | Reverse K Nodes Helper Pattern |
| find the middle, detect a cycle, or reach the n-th from the end | fast/slow pointers | one pass, O(1) space, and no length to precompute | 1-1-8), 2_pointers_linkedlist.md |
| reorder — interleave, split, rotate, palindrome-check | split with fast/slow → reverse the back half → merge | every reorder problem is those three primitives in sequence; none of them is new | examples 2), 7) |
| merge two sorted lists | dummy + a merge walk, splicing nodes rather than copying values | the tail pointer is the whole trick: cur.next = l1 or l2 finishes it |
examples 4) |
| merge k sorted lists, or sort one list | divide and conquer — pairwise merge, or merge sort via the middle | O(n log k) / O(n log n); a heap trades the recursion for O(k) space | examples 5), 14), heap.md |
| do arbitrary-position reads and O(1) eviction | doubly linked list + hash map | the map gives you the node, the doubly-linked node gives you its neighbours — neither alone is enough | Doubly Linked List + HashMap, design.md |
| do arithmetic on digits stored as a list | carry loop over a dummy, reversing first if the list is most-significant-first | the carry outlives both inputs, so the loop condition is l1 or l2 or carry |
1-1-7), examples 13) |
| answer a question that needs random access or a window | dump to an array first, then use the array technique | prefix sums and monotonic stacks need indices; a list has none, and O(n) extra space is usually allowed | examples 15), 16) |
The four traps
- Losing the list.
curr.next = prevbefore cachingcurr.nextthrows away everything downstream. Cache first — that is why the reversal loop is written in that order. - Returning the wrong head. After any operation that can touch the first node, return
dummy.next, nothead:headmay no longer be in the list. - Leaving a cycle behind. In the recursive reversal,
head.next.next = headwithout the followinghead.next = nullleaves the last two nodes pointing at each other. - Advancing past the end.
while (fast != null && fast.next != null)for a two-step hop. Getting the two clauses in the wrong order dereferences null on an even-length list.
3) Worked Examples
The full solutions moved to linked_list_examples.md so the templates above are not buried under them. Seventeen problems, grouped by the technique each one exercises:
| Group | Problems |
|---|---|
| Reversal & reordering | LC 92, 143, 24 |
| Merging & splitting | LC 21, 23, 725 |
| Fast/slow pointers & structure | LC 234, 160, 19 |
| Copying, flattening & components | LC 138, 817, 430 |
| Arithmetic & sorting | LC 369, 148, 147 |
| Array techniques borrowed onto a list | LC 1171, 1019 |