Linked Lists
Overview
Linked-list problems test pointer ownership more than indexing. Nodes are reached by following links, so insertions and deletions are constant time once the relevant predecessor is known, while random access is linear.
Most mistakes come from overwriting a link before saving it. Use a dummy head when the real head may change, preserve next before mutation, and draw the pointer relationships for reversal or reordering. Slow/fast pointers handle middle, cycle, and distance-from-end problems without extra storage.
Templates
Reverse a list
- Initialize
prev = nullandcurr = head. - Save
next = curr.next. - Redirect
curr.next = prev. - Advance
prev = currandcurr = next. - Return
prev.
public class LinkedListTemplates {
public ListNode reverse(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
} class LinkedListTemplates:
def reverse(self, head: "ListNode | None") -> "ListNode | None":
prev = None
curr = head
while curr:
next = curr.next
curr.next = prev
prev = curr
curr = next
return prev Find the first middle
- Initialize
slow = headandfast = head. - While both
fast.nextandfast.next.nextexist, advanceslowby one node andfastby two. - Return
slow. This condition returns the first middle for an even-length list and the only middle for an odd-length list.
public class LinkedListTemplates {
public ListNode firstMiddle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
} class LinkedListTemplates:
def first_middle(self, head: "ListNode | None") -> "ListNode | None":
if not head:
return None
slow = head
fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
return slow Find the second middle
- Initialize
slow = headandfast = head. - While both
fastandfast.nextexist, advanceslowby one node andfastby two. - Return
slow. This condition returns the second middle for an even-length list and the only middle for an odd-length list.
public class LinkedListTemplates {
public ListNode secondMiddle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
} class LinkedListTemplates:
def second_middle(self, head: "ListNode | None") -> "ListNode | None":
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow Delete all nodes with a target value
- Create a dummy node before
head, then initializeprev = dummyandcurr = head. - While
currexists, savenext = curr.nextbefore changing any links. - If
curr.val == target, deletecurrwithprev.next = nextand keepprevin place. - Otherwise, retain
currand advanceprev = curr. - Advance
curr = next, then returndummy.next.
public class LinkedListTemplates {
public ListNode deleteTarget(ListNode head, int target) {
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
if (curr.val == target) {
prev.next = next;
} else {
prev = curr;
}
curr = next;
}
return dummy.next;
}
} class LinkedListTemplates:
def delete_target(self, head: "ListNode | None", target: int) -> "ListNode | None":
dummy = ListNode(0, head)
prev = dummy
curr = head
while curr:
next = curr.next
if curr.val == target:
prev.next = next
else:
prev = curr
curr = next
return dummy.next Problems
19. Remove Nth Node From End of List
Remove the nth node from the end in one pass. Start slow and fast at a dummy node, then advance fast by n + 1 links. While fast != null, advance both pointers by one node. When the loop ends, slow.next is the node to remove, so bypass it with slow.next = slow.next.next.
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode slow = dummy;
ListNode fast = dummy;
for (int step = 0; step <= n; step++) {
fast = fast.next;
}
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
} class Solution:
def removeNthFromEnd(
self,
head: "ListNode | None",
n: int,
) -> "ListNode | None":
dummy = ListNode(0, head)
slow = dummy
fast = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next 141. Linked List Cycle
Detect whether following next pointers eventually revisits a node. Floyd’s tortoise-and-hare algorithm advances one pointer by one node and another by two; in a cycle, the faster pointer must eventually meet the slower one.
class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
} class Solution:
def hasCycle(self, head: "ListNode | None") -> bool:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True
return False 2. Add Two Numbers
Add two non-negative integers whose digits are stored in reverse order in linked lists. Traverse both lists together and use has1 to carry a one into the next position. Each iteration reads the available digits, separates their sum into the next carry and output digit, then appends that digit to the result. Continue while either list has a node or has1 remains. The solution takes O(max(m, n)) time and uses O(max(m, n)) space for the output list.
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode();
ListNode curr = dummy;
int has1 = 0;
while (l1 != null || l2 != null || has1 != 0) {
int d1 = 0;
int d2 = 0;
if (l1 != null) {
d1 = l1.val;
l1 = l1.next;
}
if (l2 != null) {
d2 = l2.val;
l2 = l2.next;
}
int sum = d1 + d2 + has1;
has1 = sum / 10;
int val = sum % 10;
curr.next = new ListNode(val);
curr = curr.next;
}
return dummy.next;
}
} from typing import Optional
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
dummy = curr = ListNode()
has1 = 0
while l1 or l2 or has1:
d1, d2 = 0, 0
if l1:
d1 = l1.val
l1 = l1.next
if l2:
d2 = l2.val
l2 = l2.next
has1, val = divmod(d1+d2+has1, 10)
curr.next = ListNode(val)
curr = curr.next
return dummy.next 143. Reorder List
Rearrange L0 → L1 → … → Ln into L0 → Ln → L1 → Ln-1 → …. Find the first middle node, reverse the second half, split the list after the middle, then alternate nodes from the two halves. Each phase is linear and uses constant extra space.
class Solution {
public void reorderList(ListNode head) {
ListNode firstMid = findMid(head);
ListNode reverseHead = reverseList(firstMid.next);
firstMid.next = null;
ListNode node1 = head;
ListNode node2 = reverseHead;
while (node1 != null && node2 != null) {
ListNode tmp1 = node1.next;
ListNode tmp2 = node2.next;
node1.next = node2;
node2.next = tmp1;
node1 = tmp1;
node2 = tmp2;
}
}
private ListNode findMid(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
private ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nxxt = curr.next;
curr.next = prev;
prev = curr;
curr = nxxt;
}
return prev;
}
} from typing import Optional
class Solution:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
first_mid = self.findMid(head)
reverse_head = self.reverseList(first_mid.next)
first_mid.next = None
node1, node2 = head, reverse_head
while node1 and node2:
tmp1 = node1.next
tmp2 = node2.next
node1.next = node2
node2.next = tmp1
node1, node2 = tmp1, tmp2
def findMid(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = fast = head
# finds the first mid node
# to find second mid node, use fast and fast.next
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
return slow
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev, curr = None, head
while curr:
nxxt = curr.next
curr.next = prev
prev, curr = curr, nxxt
# prev, NOT head
return prev 86. Partition List
Partition the list so every node with a value less than x appears before every node with a value greater than or equal to x, while preserving relative order within both groups. Build separate less and more chains, terminate the more chain to remove any stale link, then join the two chains. The solution runs in O(n) time and uses O(1) extra space.
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode dummyLess = new ListNode();
ListNode currLess = dummyLess;
ListNode dummyMore = new ListNode();
ListNode currMore = dummyMore;
ListNode curr = head;
while (curr != null) {
if (curr.val < x) {
currLess.next = curr;
currLess = curr;
} else {
currMore.next = curr;
currMore = curr;
}
curr = curr.next;
}
currLess.next = dummyMore.next;
dummyMore.next = null;
// don't forget this!
currMore.next = null;
return dummyLess.next;
}
} from typing import Optional
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
dummy_less = curr_less = ListNode()
dummy_more = curr_more = ListNode()
curr = head
while curr:
if curr.val < x:
curr_less.next = curr
curr_less = curr
else:
curr_more.next = curr
curr_more = curr
curr = curr.next
curr_less.next = dummy_more.next
dummy_more.next = None
# don't forget this!
curr_more.next = None
return dummy_less.next 2487. Remove Nodes From Linked List
Remove every node that has a greater value somewhere to its right. Reverse the list so the original right side is processed first, track the maximum value seen, and delete every node below that maximum using the standard prev and curr pattern. Reverse the filtered list to restore its original direction. The solution runs in O(n) time and uses O(1) extra space.
class Solution {
public ListNode removeNodes(ListNode head) {
if (head == null) {
return null;
}
ListNode reverseHead = reverseList(head);
ListNode dummy = new ListNode(0, reverseHead);
ListNode prev = dummy;
ListNode curr = reverseHead;
int maxVal = Integer.MIN_VALUE;
// Below is the standard pattern to delete nodes
while (curr != null) {
maxVal = Math.max(curr.val, maxVal);
if (curr.val < maxVal) {
prev.next = curr.next;
} else {
prev = curr;
}
curr = curr.next;
}
return reverseList(dummy.next);
}
private ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode tmp = curr.next;
curr.next = prev;
prev = curr;
curr = tmp;
}
return prev;
}
} from typing import Optional
class Solution:
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
reverse_head = self.reverseList(head)
dummy = prev = ListNode(0, reverse_head)
curr = reverse_head
max_val = float('-Inf')
# Below is the standard pattern to delete nodes
while curr:
max_val = max(curr.val, max_val)
if curr.val < max_val:
prev.next = curr.next
else:
prev = curr
curr = curr.next
return self.reverseList(dummy.next)
def reverseList(self, head):
prev, curr = None, head
while curr:
tmp = curr.next
curr.next = prev
prev, curr = curr, tmp
return prev 92. Reverse Linked List II
Reverse only the nodes from positions left through right. Use a dummy node so reversing from the head needs no special case, advance prev and curr to the start of the range, reverse exactly right - left links, then reconnect both ends of the reversed segment. The solution runs in O(n) time and uses O(1) extra space.
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy;
ListNode curr = head;
for (int i = 0; i < left - 1; i++) {
prev = curr;
curr = curr.next;
}
ListNode start = prev;
prev = curr;
curr = curr.next;
for (int i = 0; i < right - left; i++) {
ListNode tmp = curr.next;
curr.next = prev;
prev = curr;
curr = tmp;
}
start.next.next = curr;
start.next = prev;
return dummy.next;
}
} from typing import Optional
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
if not head:
return head
dummy = prev = ListNode(0, head)
curr = head
for _ in range(left - 1):
prev, curr = curr, curr.next
start = prev
prev, curr = curr, curr.next
for _ in range(right - left):
tmp = curr.next
curr.next = prev
prev, curr = curr, tmp
start.next.next = curr
start.next = prev
return dummy.next 25. Reverse Nodes in k-Group
Reverse the list in consecutive groups of k nodes while leaving an incomplete final group unchanged. Before each reversal, verify that k nodes remain after prev_tail. Reverse exactly those nodes, reconnect the previous group to the new head, connect the new tail to the next group, and advance prev_tail. The solution runs in O(n) time and uses O(1) extra space.
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null) {
return null;
}
ListNode dummy = new ListNode(0, head);
ListNode prevTail = dummy;
while (true) {
if (!hasNextK(prevTail, k)) {
break;
}
ListNode[] group = reverseNextK(prevTail, k);
ListNode currHead = group[0];
ListNode nextHead = group[1];
ListNode currTail = prevTail.next;
prevTail.next = currHead;
currTail.next = nextHead;
prevTail = currTail;
}
return dummy.next;
}
private ListNode[] reverseNextK(ListNode node, int k) {
ListNode prev = node;
ListNode curr = node.next;
int i = k;
while (i > 0) {
ListNode tmp = curr.next;
curr.next = prev;
prev = curr;
curr = tmp;
i--;
}
return new ListNode[] {prev, curr};
}
// Given a node, whether there are k nodes AFTER node
private boolean hasNextK(ListNode node, int k) {
int i = k;
while (i > 0) {
node = node.next;
if (node == null) {
break;
}
i--;
}
return i == 0;
}
} from typing import Optional
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head:
return None
dummy = prev_tail = ListNode(0, head)
while True:
if self.hasNextK(prev_tail, k) == False:
break
curr_head, next_head = self.reverseNextK(prev_tail, k)
curr_tail = prev_tail.next
prev_tail.next = curr_head
curr_tail.next = next_head
prev_tail = curr_tail
return dummy.next
def reverseNextK(self, node, k):
prev, curr = node, node.next
i = k
while i > 0:
tmp = curr.next
curr.next = prev
prev, curr = curr, tmp
i -= 1
return prev, curr
# Given a node, whether there are k nodes AFTER node
def hasNextK(self, node, k):
i = k
while i > 0:
node = node.next
if not node:
break
i -= 1
return i == 0