Introduction
Linked lists are a common topic in coding interviews. Below are five highly frequent linked list problem patterns from LeetCode, each representing a twist on basic list operations. We outline the variant name, the modified pointer technique (how it differs from basic traversal), and an example problem with a step-by-step solution.
Linked List Variants
1. In-Place Reversal of a Linked List
Modified Template: Instead of a standard forward traversal, this pattern reverses the direction of next pointers as it iterates. It uses three pointers: prev, curr, and next. Initially prev = null and curr = head. At each step, store curr.next in next, then redirect curr.next to prev (reversing the link), and move prev and curr one step forward. This in-place pointer flip continues until all nodes point backwards (medium.com). The new head will be the original tail (prev at loop end).
In-place reversal of a singly linked list. Initially, prev = ∅ and curr = 1. The pointer from 1 is flipped to point to null (new tail). Subsequently, each node’s next is reversed in turn (2’s next points to 1, 3’s next points to 2, etc.), resulting in a fully reversed list.
Example Problem: LeetCode 206 – Reverse Linked List – Given the head of a singly linked list, reverse the list and return the new head.
- Initialization:
prev = ∅,curr = 1. (List: 1 → 2 → 3 → ∅) - Iteration 1: Save
next = curr.next(node 2). Setcurr.next = prev(1’s next becomes ∅). Moveprevtocurr(prev = node 1) andcurrtonext(curr = node 2). (List now looks like 1 ← ∅ 2 → 3 → ∅; prev at 1, curr at 2) - Iteration 2: Save
next = 3. Setcurr.next = prev(2’s next -> 1). Moveprev = 2,curr = 3. (List: 1 ← 2 3 → ∅; prev at 2, curr at 3) - Iteration 3: Save
next = ∅. Setcurr.next = prev(3’s next -> 2). Moveprev = 3,curr = ∅. (List: 1 ← 2 ← 3; prev at 3, curr is null, loop ends) - Result:
prevpoints to new head (node 3). The reversed list is 3 → 2 → 1 → ∅. Every link was reversed in-place.
// Java Solution Code
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next; // Store next node
curr.next = prev; // Reverse current node's pointer
prev = curr; // Move prev one step forward
curr = nextTemp; // Move curr one step forward
}
return prev; // prev will be the new head
}
💡 Variants: Reversing sub-parts of a list follows a similar template with pointer adjustments. For example, Reverse Nodes in k-Group reverses every k nodes by repeatedly applying this technique on sublists. It requires tracking the nodes before and after each segment to reconnect the reversed parts.
2. Fast & Slow Pointers (Tortoise & Hare Technique)
Modified Template: This pattern introduces two pointers moving at different speeds through the list. A slow pointer moves one node at a time, and a fast pointer moves two nodes at a time (blog.algomaster.io). By advancing differently, we can detect certain properties:
- In a cyclic list, the
fastpointer will eventually lap theslowpointer and meet it if a cycle exists (blog.algomaster.io). - In a non-cyclic list, the
fastpointer reaches the end (null) whileslowis at the middle node.
This technique thus helps in cycle detection, finding the middle of a list, checking palindromes, etc., all in one pass.
Fast & slow pointer illustration: the fast pointer (blue) moves two steps at a time, while the slow pointer (green) moves one step. In a cyclic linked list, the fast pointer will eventually catch up to the slow pointer, indicating a cycle. (blog.algomaster.io)
Example Problem: LeetCode 141 – Linked List Cycle – Determine if a linked list contains a cycle (a node that links back to a previous node). Use O(1) extra space.
slow and fast:
- Start:
slow = 1,fast = 1. - Step 1: Move
slowto 2,fastto 3 (fast jumps two nodes) (blog.algomaster.io). (slow=2, fast=3) - Step 2: Move
slowto 3,fastto 5 (fast goes from 3 -> 4 -> 5). (slow=3, fast=5) - Step 3: Move
slowto 4,fastnext would go from 5 -> (back to) 3 -> 4, sofastbecomes 4. Nowslow == fastat node 4, meaning the fast pointer caught up to slow within the list – a cycle is confirmed. - If the list had no cycle,
fastwould hit null eventually. Here it didn’t, and instead metslow, indicating a loop.
// Java Solution Code
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true; // Pointers meet, cycle exists
}
return false; // Fast pointer reached end, no cycle
}
💡 Variants: The fast/slow pattern also finds the middle node of a list by stopping when fast hits the end (medium.com). It is used in palindrome checks (find mid, reverse second half, compare) and finding the cycle start (Floyd’s cycle algorithm extension). These use the same two-pointer concept with slight modifications in post-processing.
3. Two-Pointer Offset (Nth-from-End Technique)
Modified Template: This uses two pointers starting together but then offsetting one by n steps. By the time the leading pointer hits the end, the trailing pointer will be at the (n+1)th last node. This pattern is commonly used to remove or find the k-th node from the end in one pass (medium.com). A dummy head is often used to simplify edge cases where the head might be removed.
Procedure: Initialize two pointers fast and slow at the head. Advance fast by n nodes. Then move both fast and slow one step at a time until fast reaches the end (null). At that point, slow is right before the target node. Adjust pointers to remove or return the target node as needed (medium.com).
Example Problem: LeetCode 19 – Remove Nth Node From End of List – Remove the n-th node from the end of a linked list and return the head of the modified list.
- Initial: Use a
dummynode pointing to A.slow = dummy,fast = dummy. (List: dummy → A → B → C → D → E) - Offset
fastby n+1 steps: Movefast3 steps ahead (for n=2).fastpoints to C. (slowat dummy,fastat C). (medium.com) - Move in tandem until
fastreaches the end (fast.next == nullforfastto be at last node, orfast == nulliffastgoes beyond last node):- Move:
slowto A,fastto D. - Move:
slowto B,fastto E. fastis now at E (last node). Loop condition (while (fast != null)in typical code) will run once more.- Move:
slowto C,fastto ∅. Loop terminates.
- Move:
- Now
slowis at C, which is the node *before* the target node D. - Removal: Skip the target node: set
slow.next = slow.next.next(point C’s next to E, dropping D). The list becomes dummy → A → B → C → E. - Return
dummy.next(which is A).
// Java Solution Code
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode slow = dummy;
ListNode fast = dummy;
// Move fast n+1 steps ahead
for (int i = 0; i <= n; i++) {
if (fast == null) { // Handles cases where n is too large or list is short
return head; // Or throw error, depending on problem spec for invalid n
}
fast = fast.next;
}
// Move both pointers until fast reaches the end of the list
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
// Now slow is the node just before the Nth node from the end
if (slow != null && slow.next != null) {
slow.next = slow.next.next; // Skip the Nth node
}
return dummy.next;
}
💡 Variants: The two-pointer offset pattern also helps find the k-th node from the end (without removal). A related trick is finding the intersection of two linked lists: by advancing two pointers and then resetting one to the other list’s head, they align after the length difference and meet at the intersection node (medium.com). Both techniques rely on offsetting pointer positions to sync up at a target.
4. Dummy Head & Sorted Merge Pattern
Modified Template: Many list problems are easier if you use a dummy head (sentinel) to handle edge cases uniformly. In merging or constructing new lists, the dummy’s next will eventually be the real head of the result. Maintain a tail pointer starting at the dummy. Iterate through input lists, and at each step, attach the next node to tail.next and advance tail. This avoids having to treat the first node as a special case since the dummy precedes the result list (medium.com).
Example Problem: LeetCode 21 – Merge Two Sorted Lists – Merge two sorted linked lists into one sorted list.
dummy node (with value say 0) and let tail = dummy:
- Start:
tailatdummy(∅ → dummy). List1 at 1, List2 at 2. - Compare List1 and List2 heads (1 vs 2). Attach smaller (1) to
tail.next. Now result list is dummy → 1. Advance List1 pointer to 3. Movetailto the newly added node (tail= node 1). - Compare List1 (3) vs List2 (2). Attach smaller (2) to
tail.next. Result becomes dummy → 1 → 2. Advance List2 to 4, movetailto node 2. - Compare 3 vs 4. Attach 3 → result dummy → 1 → 2 → 3. Advance List1 to 5,
tailto 3. - Compare 5 vs 4. Attach 4 → result ... → 3 → 4. Advance List2 to 6,
tailto 4. - Compare 5 vs 6. Attach 5 → result ... → 4 → 5. Advance List1 to ∅ (end),
tailto 5. - One list is exhausted (List1). Append remaining List2 (node 6) to result. Result becomes dummy → 1 → 2 → 3 → 4 → 5 → 6.
- Return
dummy.next(head of merged list, node 1). The merged list (excluding dummy) is 1 → 2 → 3 → 4 → 5 → 6.
// Java Solution Code
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
// Attach whichever list remains
if (l1 != null) {
tail.next = l1;
} else {
tail.next = l2;
}
return dummy.next;
}
💡 Variants: Using a dummy head simplifies list insertion and combination problems. It appears in adding numbers represented by lists (to build the sum list) (medium.com, medium.com), in partitioning a list (building two sublists then joining), and in many merge-related tasks. The dummy is a convenient placeholder to avoid handling empty-result or head-update cases separately.
5. Cloning a List with Random/Arbitrary Pointers
Modified Template: This covers linked lists that have extra pointers besides the normal next (e.g. a random pointer that can point to any node or null). The challenge is to copy the complex pointers structure. A typical solution uses multiple passes with pointer manipulation:
- Interweave clone nodes: For each original node, create a new node and insert it right after the original node in the list (algo.monster). This weaves the original and cloned nodes in one list.
- Set random pointers: Traverse the interwoven list and for each original node, set its clone’s
randompointer. If original’srandompoints to node X, then clone’srandomshould point to X’s clone (which isX.nextin the interwoven list) (algo.monster). - Detach the clone list: Restore the original list and extract the cloned nodes by reconnecting clone nodes together in their own list (algo.monster).
Example Problem: LeetCode 138 – Copy List with Random Pointer – Each list node has an additional random pointer to any node (or null). Return a deep copy of the list.
- Interleave Clones: Clone each node and insert right after it:
- Clone of 1 (call it 1′) inserted after 1.
- Clone of 2 (2′) inserted after 2.
- Clone of 3 (3′) inserted after 3.
- Set Random Pointers: Iterate again:
- For original 1, which had
random → 2, set1′.random → 2′(the clone of 2) (algo.monster). - Original 2’s
randomis null, so2′.random = null. - Original 3’s
random → 1, so set3′.random → 1′.
- For original 1, which had
- Detach Lists: Split the interwoven list into two:
- Restore original list: 1 → 2 → 3 → ∅ (by skipping out the clones).
- Extract cloned list: 1′ → 2′ → 3′ → ∅ (with clone pointers all set) (algo.monster).
# Python Solution Code
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return None
# 1. Interleave cloned nodes with original nodes
curr = head
while curr:
newNode = Node(curr.val)
newNode.next = curr.next
curr.next = newNode
curr = newNode.next
# 2. Set random pointers of clones
curr = head
while curr:
if curr.random:
curr.next.random = curr.random.next # curr.random.next is the clone of curr.random
curr = curr.next.next # Move to the next original node
# 3. Detach the clone list from the original list
curr = head
clone_head = head.next
while curr:
clone_node = curr.next
curr.next = clone_node.next # Restore original list's next pointer
if clone_node.next:
clone_node.next = clone_node.next.next # Set clone list's next pointer
curr = curr.next # Move to the next original node
return clone_head
💡 Variants: Flattening a multilevel linked list (where each node may have a child list) is another pointer-intensive problem. It can be solved by traversing depth-first and splicing child lists into the main list (medium.com, medium.com). Design problems like LRU Cache use a doubly-linked list with extra pointers for quick eviction. Whenever nodes have additional pointers or multi-level structure, the solution often involves carefully updating those pointers in-place (sometimes using a stack or dummy nodes) to achieve the desired list transformation.
Sources: Key patterns and examples are drawn from popular LeetCode interview questions (linkedin.com, linkedin.com) and canonical solutions (medium.com, medium.com, medium.com, medium.com). Techniques like fast/slow pointers and in-place reversal are well-documented for cycle detection (blog.algomaster.io) and list manipulation (blog.algomaster.io). The random-pointer cloning strategy is described in-depth in problem explanations (algo.monster, algo.monster). These patterns cover the most frequent and insightful Linked List variations seen in coding interviews.