Linked List Manipulation Patterns
1. Reverse Linked List (Iterative and Recursive)
| Approach | Technique | Time | Space |
|---|---|---|---|
| Iterative | Three pointers: prev, curr, next. Reverse links one by one | O(n) |
O(1) |
| Recursive | Reverse rest of list, then fix current node's next pointer | O(n) |
O(n) stack |
| Tail Recursive | Pass accumulator (reversed part) through recursive calls | O(n) |
O(n) stack |
| Reverse Sublist | Reverse portion [m, n], reconnect with unchanged parts | O(n) |
O(1) |
Example: Iterative reversal
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function reverseListIterative(head) {
let prev = null;
let curr = head;
while (curr !== null) {
const next = curr.next; // Save next
curr.next = prev; // Reverse link
prev = curr; // Move prev forward
curr = next; // Move curr forward
}
return prev; // New head
}
// Example: 1->2->3->4->5
// Result: 5->4->3->2->1
// Create list
let head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head = reverseListIterative(head);
// Now: 3->2->1
Example: Recursive reversal
function reverseListRecursive(head) {
// Base case
if (head === null || head.next === null) {
return head;
}
// Reverse rest of list
const newHead = reverseListRecursive(head.next);
// Fix current node
head.next.next = head; // Point next node back to current
head.next = null; // Remove forward link
return newHead;
}
// Tail recursive version
function reverseListTailRecursive(head, prev = null) {
if (head === null) {
return prev;
}
const next = head.next;
head.next = prev;
return reverseListTailRecursive(next, head);
}
// Both produce same result: 3->2->1
Example: Reverse sublist [m, n]
function reverseBetween(head, left, right) {
if (left === right) return head;
const dummy = new ListNode(0, head);
let prev = dummy;
// Move to node before left position
for (let i = 1; i < left; i++) {
prev = prev.next;
}
// Reverse the sublist
let curr = prev.next;
for (let i = 0; i < right - left; i++) {
const next = curr.next;
curr.next = next.next;
next.next = prev.next;
prev.next = next;
}
return dummy.next;
}
// Example: 1->2->3->4->5, left=2, right=4
// Result: 1->4->3->2->5
2. Merge Two Sorted Linked Lists
| Approach | Method | Time | Space |
|---|---|---|---|
| Iterative | Compare heads, append smaller, advance pointer | O(n + m) |
O(1) |
| Recursive | Choose smaller head, merge rest recursively | O(n + m) |
O(n + m) stack |
| Dummy Head | Use dummy node to simplify edge cases | O(n + m) |
O(1) |
| In-place | Rearrange nodes without creating new nodes | O(n + m) |
O(1) |
Example: Merge iterative with dummy
function mergeTwoLists(l1, l2) {
const dummy = new ListNode(0);
let curr = dummy;
while (l1 !== null && l2 !== null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
// Append remaining nodes
curr.next = l1 !== null ? l1 : l2;
return dummy.next;
}
// Example:
// l1: 1->3->5
// l2: 2->4->6
// Result: 1->2->3->4->5->6
Example: Merge recursive
function mergeTwoListsRecursive(l1, l2) {
// Base cases
if (l1 === null) return l2;
if (l2 === null) return l1;
// Choose smaller head
if (l1.val <= l2.val) {
l1.next = mergeTwoListsRecursive(
l1.next,
l2
);
return l1;
} else {
l2.next = mergeTwoListsRecursive(
l1,
l2.next
);
return l2;
}
}
// Elegant recursive solution
// Each call handles one node
3. Remove Nth Node From End of List
| Technique | Strategy | Passes | Edge Cases |
|---|---|---|---|
| Two Pass | Count length, then remove (length - n)th node | 2 |
Handle when n = length (remove head) |
| One Pass (Two Pointers) | Fast pointer n steps ahead, move both until fast reaches end | 1 |
Use dummy head for edge cases |
| Dummy Node Trick | Add dummy before head to handle head removal uniformly | 1 |
Simplifies all edge cases |
| Recursive | Return count from end, remove when count = n | 1 |
Uses call stack space |
Example: Remove Nth from end (one pass)
function removeNthFromEnd(head, n) {
const dummy = new ListNode(0, head);
let fast = dummy;
let slow = dummy;
// Move fast n+1 steps ahead
for (let i = 0; i <= n; i++) {
fast = fast.next;
}
// Move both until fast reaches end
while (fast !== null) {
fast = fast.next;
slow = slow.next;
}
// Remove nth node
slow.next = slow.next.next;
return dummy.next;
}
// Example: 1->2->3->4->5, n=2
// Remove 4 (2nd from end)
// Result: 1->2->3->5
// Key insight: Gap of n between fast and slow
// When fast reaches end, slow is at node before target
Note: The dummy node pattern is crucial for linked list problems. It eliminates special cases for removing the head node and simplifies code significantly.
4. Reorder Linked List Pattern
| Step | Action | Pattern Used | Purpose |
|---|---|---|---|
| 1. Find Middle | Use slow-fast pointers to find middle node | Fast & slow pointers | Split list into two halves |
| 2. Reverse Second Half | Reverse the second half of list | Reverse linked list | Prepare for interleaving |
| 3. Merge Alternately | Merge first and reversed second half | Two pointers merge | Achieve L0→Ln→L1→Ln-1 pattern |
| Edge Cases | Handle odd/even length, single node, empty list | Boundary checks | Ensure correctness |
Example: Reorder list (L0→Ln→L1→Ln-1...)
function reorderList(head) {
if (!head || !head.next) return;
// Step 1: Find middle
let slow = head, fast = head;
while (fast.next && fast.next.next) {
slow = slow.next;
fast = fast.next.next;
}
// Step 2: Reverse second half
let second = slow.next;
slow.next = null; // Split list
let prev = null;
while (second) {
const next = second.next;
second.next = prev;
prev = second;
second = next;
}
second = prev; // Head of reversed second half
// Step 3: Merge alternately
let first = head;
while (second) {
const tmp1 = first.next;
const tmp2 = second.next;
first.next = second;
second.next = tmp1;
first = tmp1;
second = tmp2;
}
}
// Example: 1->2->3->4->5
// Result: 1->5->2->4->3
5. Copy List with Random Pointer (Deep Copy)
| Approach | Strategy | Time | Space |
|---|---|---|---|
| Hash Map | Map old nodes to new nodes, then set next/random pointers | O(n) |
O(n) |
| Interleaving | Insert copied nodes after originals, set random, then separate | O(n) |
O(1) |
| Two Pass Hash | First pass: create nodes, second pass: set pointers | O(n) |
O(n) |
| Recursive | Clone using recursion with memoization map | O(n) |
O(n) |
Example: Copy using hash map
class RandomListNode {
constructor(val, next = null, random = null) {
this.val = val;
this.next = next;
this.random = random;
}
}
function copyRandomList(head) {
if (!head) return null;
const map = new Map();
// First pass: create all nodes
let curr = head;
while (curr) {
map.set(curr, new RandomListNode(curr.val));
curr = curr.next;
}
// Second pass: set next and random pointers
curr = head;
while (curr) {
const newNode = map.get(curr);
newNode.next = map.get(curr.next) || null;
newNode.random = map.get(curr.random) || null;
curr = curr.next;
}
return map.get(head);
}
Example: Copy with O(1) space (interleaving)
function copyRandomListO1(head) {
if (!head) return null;
// Step 1: Create interleaved list
// A->B->C becomes A->A'->B->B'->C->C'
let curr = head;
while (curr) {
const copy = new RandomListNode(curr.val);
copy.next = curr.next;
curr.next = copy;
curr = copy.next;
}
// Step 2: Set random pointers
curr = head;
while (curr) {
if (curr.random) {
curr.next.random = curr.random.next;
}
curr = curr.next.next;
}
// Step 3: Separate lists
const newHead = head.next;
curr = head;
while (curr) {
const copy = curr.next;
curr.next = copy.next;
copy.next = copy.next ? copy.next.next : null;
curr = curr.next;
}
return newHead;
}
6. Flatten Multilevel Doubly Linked List
| Structure Type | Approach | Traversal | Complexity |
|---|---|---|---|
| Doubly Linked with Child | DFS: process child before next, reconnect pointers | Depth-first | O(n) time, O(n) space |
| Iterative Stack | Use stack to save next pointer when child exists | Explicit stack | O(n) time, O(n) space |
| Recursive | Flatten child, attach to current, continue with next | Recursive DFS | O(n) time, O(d) depth |
| Two Pointer | Current pointer + find tail of child list | Iterative | O(n) time, O(1) space |
Example: Flatten doubly linked list with child pointer
class DoublyListNode {
constructor(val, prev = null, next = null, child = null) {
this.val = val;
this.prev = prev;
this.next = next;
this.child = child;
}
}
function flatten(head) {
if (!head) return head;
const stack = [];
let curr = head;
while (curr) {
// If child exists, process it first
if (curr.child) {
// Save next for later
if (curr.next) {
stack.push(curr.next);
}
// Connect child
curr.next = curr.child;
curr.child.prev = curr;
curr.child = null;
}
// If no next but stack has nodes, pop from stack
if (!curr.next && stack.length > 0) {
const next = stack.pop();
curr.next = next;
next.prev = curr;
}
curr = curr.next;
}
return head;
}
// Example:
// 1->2->3->4
// |
// 7->8
// |
// 11->12
//
// Flattened: 1->2->7->11->12->8->3->4
Example: Flatten recursive approach
function flattenRecursive(head) {
if (!head) return head;
// Helper returns tail of flattened list
function flattenDFS(node) {
let curr = node;
let tail = node;
while (curr) {
const next = curr.next;
if (curr.child) {
// Flatten child
const childTail = flattenDFS(curr.child);
// Connect child
curr.next = curr.child;
curr.child.prev = curr;
curr.child = null;
// Connect tail of child to next
if (next) {
childTail.next = next;
next.prev = childTail;
}
tail = childTail;
}
if (next) {
tail = next;
}
curr = next;
}
return tail;
}
flattenDFS(head);
return head;
}
// Cleaner recursive solution
// Processes entire subtree before moving to next
Note: When flattening multilevel lists:
- Always process child before next for depth-first traversal
- Use stack to save next pointer when diving into child
- Set child pointer to null after flattening to avoid cycles
- Update both prev and next pointers for doubly linked lists
Summary: Linked List Manipulation Patterns
- Reversal Pattern: Three pointers (prev, curr, next) for iterative O(1) space; recursive uses O(n) stack but is elegant
- Dummy Head Trick: Add dummy node before head to handle edge cases uniformly, especially for removing head or empty lists
- Two Pointers: Fast-slow for finding middle, gap of n for nth from end, merge pattern for sorted lists
- Reverse Sublist: Find boundaries, reverse middle portion, reconnect - all in one pass
- Merge Sorted: Compare heads, append smaller, advance pointer - works iteratively O(1) or recursively O(n) space
- Remove Nth from End: One-pass with two pointers: fast moves n+1 ahead, then both move until fast reaches end
- Reorder List: Three-step pattern: find middle, reverse second half, merge alternately
- Copy with Random: Hash map O(n) space or interleaving O(1) space - both O(n) time with two passes
- Flatten Multilevel: DFS pattern - process child before next, use stack for iterative or recursion for elegant solution
- Common Edge Cases: Empty list, single node, removing head, odd/even length, cycles
- Space Optimization: Prefer iterative over recursive for O(1) space when possible
- Pointer Management: Always update both prev and next for doubly linked lists; set child/random to null after processing