Linked Lists
Nodes chained by pointers: O(1) to insert or delete where you stand, O(n) to find anything. Master reversal, dummy heads and fast and slow pointers.
How It Works
A linked list stores each value in its own node, and each node holds a pointer to the next one. The list itself is just a pointer to the first node, the head, and the last node points to null. The nodes aren't stored side by side, so there's no indexing: to reach the fifth node you start at the head and follow four next pointers.
head | v +---+---+ +---+---+ +---+---+ | 1 | o-+--->| 2 | o-+--->| 3 | o-+---> null +---+---+ +---+---+ +---+---+ val next
That's the trade against an array. Reaching position k costs O(k) instead of O(1). In exchange, once you're holding a node, inserting or removing next to it is O(1), because you rewrite a pointer or two and nothing else moves. An array would shift every later element. To insert x after node, set x.next = node.next and then node.next = x, in that order. To remove the node after node, set node.next = node.next.next.
Most linked list problems are built from three moves:
- Reverse in place. Walk the list with
prevandcurr, pointing eachnextbackwards as you go. That's the template below, and it shows up inside harder problems such as reordering a list or checking whether it's a palindrome. - Dummy head. Put a placeholder node in front of the result, so adding or removing the first real node isn't a special case, and return
dummy.nextat the end. Merge Two Sorted Lists uses one. - Fast and slow pointers. Move one pointer two nodes for every one the other moves. When the fast one reaches the end, the slow one is at the middle, and if the list loops, they meet inside the loop. It's the two pointers idea adapted to lists.
Reversing a List in Place
Step 1 of 11
- curr, the node being flipped
- nextNode, the rest of the list
- Reversed part
prev starts at null, because nothing is reversed yet, and curr starts at the head, 1. Each pass flips one arrow.
All 11 steps as text
prevstarts atnull, because nothing is reversed yet, andcurrstarts at the head,1. Each pass flips one arrow.- Save
nextNode = curr.next, which is2. The next line overwritescurr.next, and without this pointer you'd lose the rest of the list. - Point
curr.nextback atprev.1now points atnull, because the old head becomes the new tail. - Step both forward:
prevmoves to1andcurrto2. Everything left ofcurris reversed now. - Save
nextNode = curr.next, which is3, before the flip overwrites it. - Point
curr.nextback atprev.2now points at1, so the reversed part reads2 -> 1 -> null. - Step both forward:
prevmoves to2andcurrto3. Everything left ofcurris reversed now. - Save
nextNode = curr.next, which isnullbecause3is the last node. The loop body is the same as before, with no special case. - Point
curr.nextback atprev.3now points at2, so the reversed part reads3 -> 2 -> 1 -> null. - Step both forward:
prevmoves to3andcurrtonull.currhas run off the end, so the loop stops. - Return
prevas the new head. The old tail3comes first now, and the list reads3 -> 2 -> 1 -> null. You touched each node once, with O(1) extra space.
When to Reach for It
- The input is a
headnode rather than an array. - You must rearrange nodes in place (reverse, reorder, merge or remove them) without copying the values into an array.
- The question asks for the middle node, the kth node from the end, or whether the list loops, often in O(1) extra space.
- You need O(1) inserts and deletes in the middle of a sequence, as in an LRU cache, which pairs a doubly linked list with a hash map.
Complexity
| Operation | Time | Space |
|---|---|---|
| Reach the kth node | O(k) | O(1) |
| Search for a value | O(n) | O(1) |
| Insert or delete next to a node you hold | O(1) | O(1) |
| Insert at the head | O(1) | O(1) |
| Reverse in place | O(n) | O(1) |
The Template
Build, Walk and Reverse a List
Each file defines a small ListNode with a value and a next pointer, then three functions. buildList creates nodes from the back of the array, so each new node can point at the list built so far. That's O(1) per node with no tail pointer. formatList is the standard walk: start at head and follow next until you reach null.
reverseList flips the list in place. curr is the node being flipped, and prev is the reversed part behind it. For each node, save curr.next first, because the next line overwrites it and you'd lose the rest of the list. Then point curr back at prev and step both forward.
start null 1 -> 2 -> 3 -> null
prev curr
step 1 null <- 1 2 -> 3 -> null
prev curr
step 2 null <- 1 <- 2 3 -> null
prev curr
step 3 null <- 1 <- 2 <- 3 null
prev currcurr runs off the end, prev is the new head.Time O(n)Space O(1) to walk or reverse, O(n) to build
from __future__ import annotations
class ListNode:
"""One node of a singly linked list."""
def __init__(self, val: int, next: ListNode | None = None):
self.val = val
self.next = next
def build_list(values: list[int]) -> ListNode | None:
"""Builds a list from `values`. Prepending from the back is O(1) per node."""
head = None
for value in reversed(values):
head = ListNode(value, head)
return head
def format_list(head: ListNode | None) -> str:
"""Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> None"."""
parts = []
node = head
while node is not None:
parts.append(str(node.val))
node = node.next
parts.append("None")
return " -> ".join(parts)
def reverse_list(head: ListNode | None) -> ListNode | None:
"""Reverses the list in place and returns the new head (the old tail)."""
prev = None
curr = head
while curr is not None:
next_node = curr.next # Save the rest of the list before cutting the link.
curr.next = prev # Point this node back at the one before it.
prev = curr
curr = next_node
return prev # curr has walked off the end, so prev is the old tail.
if __name__ == "__main__":
head = build_list([1, 2, 3, 4, 5])
print(format_list(head)) # 1 -> 2 -> 3 -> 4 -> 5 -> None
print(format_list(reverse_list(head))) # 5 -> 4 -> 3 -> 2 -> 1 -> None
print(format_list(reverse_list(build_list([7])))) # 7 -> None
print(format_list(reverse_list(build_list([])))) # None// One node of a singly linked list.
export class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
// Builds a list from values. Prepending from the back is O(1) per node.
export function buildList(values: number[]): ListNode | null {
let head: ListNode | null = null;
for (let i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
// Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
export function formatList(head: ListNode | null): string {
const parts: string[] = [];
for (let node = head; node !== null; node = node.next) parts.push(String(node.val));
parts.push("null");
return parts.join(" -> ");
}
// Reverses the list in place and returns the new head (the old tail).
export function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr = head;
while (curr !== null) {
const nextNode = curr.next; // Save the rest of the list before cutting the link.
curr.next = prev; // Point this node back at the one before it.
prev = curr;
curr = nextNode;
}
return prev; // curr has walked off the end, so prev is the old tail.
}
const head = buildList([1, 2, 3, 4, 5]);
console.log(formatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
console.log(formatList(reverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
console.log(formatList(reverseList(buildList([7])))); // 7 -> null
console.log(formatList(reverseList(buildList([])))); // nullpackage main
import (
"fmt"
"strconv"
"strings"
)
// ListNode is one node of a singly linked list.
type ListNode struct {
Val int
Next *ListNode
}
// buildList builds a list from values. Prepending from the back is O(1) per
// node.
func buildList(values []int) *ListNode {
var head *ListNode
for i := len(values) - 1; i >= 0; i-- {
head = &ListNode{Val: values[i], Next: head}
}
return head
}
// formatList walks the list from head to tail, e.g. "1 -> 2 -> 3 -> nil".
func formatList(head *ListNode) string {
parts := []string{}
for node := head; node != nil; node = node.Next {
parts = append(parts, strconv.Itoa(node.Val))
}
parts = append(parts, "nil")
return strings.Join(parts, " -> ")
}
// reverseList reverses the list in place and returns the new head (the old
// tail).
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
nextNode := curr.Next // Save the rest of the list before cutting the link.
curr.Next = prev // Point this node back at the one before it.
prev = curr
curr = nextNode
}
return prev // curr has walked off the end, so prev is the old tail.
}
func main() {
head := buildList([]int{1, 2, 3, 4, 5})
fmt.Println(formatList(head)) // 1 -> 2 -> 3 -> 4 -> 5 -> nil
fmt.Println(formatList(reverseList(head))) // 5 -> 4 -> 3 -> 2 -> 1 -> nil
fmt.Println(formatList(reverseList(buildList([]int{7})))) // 7 -> nil
fmt.Println(formatList(reverseList(buildList([]int{})))) // nil
}using System;
using System.Collections.Generic;
public class Solution
{
// One node of a singly linked list.
public class ListNode
{
public int Val;
public ListNode? Next;
public ListNode(int val, ListNode? next = null)
{
Val = val;
Next = next;
}
}
// Builds a list from values. Prepending from the back is O(1) per node.
public static ListNode? BuildList(int[] values)
{
ListNode? head = null;
for (int i = values.Length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
// Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
public static string FormatList(ListNode? head)
{
var parts = new List<string>();
for (var node = head; node != null; node = node.Next) parts.Add(node.Val.ToString());
parts.Add("null");
return string.Join(" -> ", parts);
}
// Reverses the list in place and returns the new head (the old tail).
public static ListNode? ReverseList(ListNode? head)
{
ListNode? prev = null;
var curr = head;
while (curr != null)
{
var nextNode = curr.Next; // Save the rest of the list before cutting the link.
curr.Next = prev; // Point this node back at the one before it.
prev = curr;
curr = nextNode;
}
return prev; // curr has walked off the end, so prev is the old tail.
}
public static void Main()
{
var head = BuildList(new[] { 1, 2, 3, 4, 5 });
Console.WriteLine(FormatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
Console.WriteLine(FormatList(ReverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
Console.WriteLine(FormatList(ReverseList(BuildList(new[] { 7 })))); // 7 -> null
Console.WriteLine(FormatList(ReverseList(BuildList(new int[0])))); // null
}
}import java.util.ArrayList;
import java.util.List;
public class Solution {
// One node of a singly linked list.
static class ListNode {
int val;
ListNode next;
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
// Builds a list from values. Prepending from the back is O(1) per node.
static ListNode buildList(int[] values) {
ListNode head = null;
for (int i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
// Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
static String formatList(ListNode head) {
List<String> parts = new ArrayList<>();
for (ListNode node = head; node != null; node = node.next) parts.add(String.valueOf(node.val));
parts.add("null");
return String.join(" -> ", parts);
}
// Reverses the list in place and returns the new head (the old tail).
static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextNode = curr.next; // Save the rest of the list before cutting the link.
curr.next = prev; // Point this node back at the one before it.
prev = curr;
curr = nextNode;
}
return prev; // curr has walked off the end, so prev is the old tail.
}
public static void main(String[] args) {
ListNode head = buildList(new int[] {1, 2, 3, 4, 5});
System.out.println(formatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
System.out.println(formatList(reverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
System.out.println(formatList(reverseList(buildList(new int[] {7})))); // 7 -> null
System.out.println(formatList(reverseList(buildList(new int[] {})))); // null
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
You get the heads of two linked lists, each sorted in ascending order. Splice their nodes together into one sorted list and return its head. Reuse the existing nodes rather than creating new ones.
Example: 1 -> 2 -> 4 and 1 -> 3 -> 4 merge into 1 -> 1 -> 2 -> 3 -> 4 -> 4.
Show SolutionHide Solution
Approach
This is the merge step of merge sort, done with pointers. Compare the two heads, append the smaller one to the result, and advance that list. A dummy node in front of the result means there's always a tail to append to, so an empty result doesn't need its own branch. When one list runs out, the other is already sorted, so you attach the rest of it in one step.
Taking from a on a tie (<=) keeps equal values in their original order. That makes the merge stable, which matters when the nodes carry more than a number. With lists of length n and m, each node is appended once, and the only new node is the dummy.
Time O(n + m)Space O(1)
from __future__ import annotations
class ListNode:
"""One node of a singly linked list."""
def __init__(self, val: int, next: ListNode | None = None):
self.val = val
self.next = next
def merge_two_lists(a: ListNode | None, b: ListNode | None) -> ListNode | None:
"""Merges two sorted lists into one sorted list by relinking their nodes."""
# A placeholder in front of the result, so the first append isn't a special case.
dummy = ListNode(0)
tail = dummy
while a is not None and b is not None:
# Take the smaller head. On a tie take from a, so equal values keep their order.
if a.val <= b.val:
tail.next = a
tail = a
a = a.next
else:
tail.next = b
tail = b
b = b.next
# One list ran out. The other is already sorted, so attach the rest of it whole.
tail.next = a if a is not None else b
return dummy.next
def build_list(values: list[int]) -> ListNode | None:
head = None
for value in reversed(values):
head = ListNode(value, head)
return head
def format_list(head: ListNode | None) -> str:
parts = []
node = head
while node is not None:
parts.append(str(node.val))
node = node.next
parts.append("None")
return " -> ".join(parts)
if __name__ == "__main__":
print(format_list(merge_two_lists(build_list([1, 2, 4]), build_list([1, 3, 4])))) # 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
print(format_list(merge_two_lists(build_list([]), build_list([])))) # None
print(format_list(merge_two_lists(build_list([]), build_list([0])))) # 0 -> None// One node of a singly linked list.
export class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
// Merges two sorted lists into one sorted list by relinking their nodes.
export function mergeTwoLists(a: ListNode | null, b: ListNode | null): ListNode | null {
// A placeholder in front of the result, so the first append isn't a special case.
const dummy = new ListNode(0);
let tail = dummy;
while (a !== null && b !== null) {
// Take the smaller head. On a tie take from a, so equal values keep their order.
if (a.val <= b.val) {
tail.next = a;
tail = a;
a = a.next;
} else {
tail.next = b;
tail = b;
b = b.next;
}
}
// One list ran out. The other is already sorted, so attach the rest of it whole.
tail.next = a ?? b;
return dummy.next;
}
function buildList(values: number[]): ListNode | null {
let head: ListNode | null = null;
for (let i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
function formatList(head: ListNode | null): string {
const parts: string[] = [];
for (let node = head; node !== null; node = node.next) parts.push(String(node.val));
parts.push("null");
return parts.join(" -> ");
}
console.log(formatList(mergeTwoLists(buildList([1, 2, 4]), buildList([1, 3, 4])))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
console.log(formatList(mergeTwoLists(buildList([]), buildList([])))); // null
console.log(formatList(mergeTwoLists(buildList([]), buildList([0])))); // 0 -> nullpackage main
import (
"fmt"
"strconv"
"strings"
)
// ListNode is one node of a singly linked list.
type ListNode struct {
Val int
Next *ListNode
}
// mergeTwoLists merges two sorted lists into one sorted list by relinking
// their nodes.
func mergeTwoLists(a, b *ListNode) *ListNode {
// A placeholder in front of the result, so the first append isn't a special case.
dummy := &ListNode{}
tail := dummy
for a != nil && b != nil {
// Take the smaller head. On a tie take from a, so equal values keep their order.
if a.Val <= b.Val {
tail.Next = a
tail = a
a = a.Next
} else {
tail.Next = b
tail = b
b = b.Next
}
}
// One list ran out. The other is already sorted, so attach the rest of it whole.
if a != nil {
tail.Next = a
} else {
tail.Next = b
}
return dummy.Next
}
func buildList(values []int) *ListNode {
var head *ListNode
for i := len(values) - 1; i >= 0; i-- {
head = &ListNode{Val: values[i], Next: head}
}
return head
}
func formatList(head *ListNode) string {
parts := []string{}
for node := head; node != nil; node = node.Next {
parts = append(parts, strconv.Itoa(node.Val))
}
parts = append(parts, "nil")
return strings.Join(parts, " -> ")
}
func main() {
fmt.Println(formatList(mergeTwoLists(buildList([]int{1, 2, 4}), buildList([]int{1, 3, 4})))) // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> nil
fmt.Println(formatList(mergeTwoLists(buildList([]int{}), buildList([]int{})))) // nil
fmt.Println(formatList(mergeTwoLists(buildList([]int{}), buildList([]int{0})))) // 0 -> nil
}using System;
using System.Collections.Generic;
public class Solution
{
// One node of a singly linked list.
public class ListNode
{
public int Val;
public ListNode? Next;
public ListNode(int val, ListNode? next = null)
{
Val = val;
Next = next;
}
}
// Merges two sorted lists into one sorted list by relinking their nodes.
public static ListNode? MergeTwoLists(ListNode? a, ListNode? b)
{
// A placeholder in front of the result, so the first append isn't a special case.
var dummy = new ListNode(0);
var tail = dummy;
while (a != null && b != null)
{
// Take the smaller head. On a tie take from a, so equal values keep their order.
if (a.Val <= b.Val)
{
tail.Next = a;
tail = a;
a = a.Next;
}
else
{
tail.Next = b;
tail = b;
b = b.Next;
}
}
// One list ran out. The other is already sorted, so attach the rest of it whole.
tail.Next = a ?? b;
return dummy.Next;
}
static ListNode? BuildList(int[] values)
{
ListNode? head = null;
for (int i = values.Length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
static string FormatList(ListNode? head)
{
var parts = new List<string>();
for (var node = head; node != null; node = node.Next) parts.Add(node.Val.ToString());
parts.Add("null");
return string.Join(" -> ", parts);
}
public static void Main()
{
Console.WriteLine(FormatList(MergeTwoLists(BuildList(new[] { 1, 2, 4 }), BuildList(new[] { 1, 3, 4 })))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
Console.WriteLine(FormatList(MergeTwoLists(BuildList(new int[0]), BuildList(new int[0])))); // null
Console.WriteLine(FormatList(MergeTwoLists(BuildList(new int[0]), BuildList(new[] { 0 })))); // 0 -> null
}
}import java.util.ArrayList;
import java.util.List;
public class Solution {
// One node of a singly linked list.
static class ListNode {
int val;
ListNode next;
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
// Merges two sorted lists into one sorted list by relinking their nodes.
static ListNode mergeTwoLists(ListNode a, ListNode b) {
// A placeholder in front of the result, so the first append isn't a special case.
ListNode dummy = new ListNode(0, null);
ListNode tail = dummy;
while (a != null && b != null) {
// Take the smaller head. On a tie take from a, so equal values keep their order.
if (a.val <= b.val) {
tail.next = a;
tail = a;
a = a.next;
} else {
tail.next = b;
tail = b;
b = b.next;
}
}
// One list ran out. The other is already sorted, so attach the rest of it whole.
tail.next = a != null ? a : b;
return dummy.next;
}
static ListNode buildList(int[] values) {
ListNode head = null;
for (int i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
return head;
}
static String formatList(ListNode head) {
List<String> parts = new ArrayList<>();
for (ListNode node = head; node != null; node = node.next) parts.add(String.valueOf(node.val));
parts.add("null");
return String.join(" -> ", parts);
}
public static void main(String[] args) {
System.out.println(formatList(mergeTwoLists(buildList(new int[] {1, 2, 4}), buildList(new int[] {1, 3, 4})))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
System.out.println(formatList(mergeTwoLists(buildList(new int[] {}), buildList(new int[] {})))); // null
System.out.println(formatList(mergeTwoLists(buildList(new int[] {}), buildList(new int[] {0})))); // 0 -> null
}
}Problem
Given the head of a linked list, return whether it has a cycle: a node whose next pointer leads back to a node you've already passed, so walking the list never ends.
Example: in 3 -> 2 -> 0 -> -4, where -4 points back to 2, the answer is true. LeetCode writes that list as head = [3, 2, 0, -4], pos = 1, where pos is the index the tail links back to, or -1 for no cycle. The demo builds its lists the same way.
Show SolutionHide Solution
Approach
The easy answer is a hash set of visited nodes: see one twice and there's a cycle. That costs O(n) extra space. Floyd's algorithm does it in O(1). Move slow one node per step and fast two. Without a cycle, fast reaches the end and you return false.
With a cycle, both pointers end up going around it. From then on, fast gains exactly one node on slow per step, so the gap between them shrinks by one each time. It can't jump over slow, because the gap only ever drops by one, so they meet within one lap. slow enters the cycle within n steps and they meet less than one lap later, which keeps the whole thing O(n).
3 -> 2 -> 0 -> -4
^ |
+---------+
step slow fast
0 3 3
1 2 0
2 0 2
3 -4 -4 same nodefast laps around the cycle and lands on slow after three steps.Fast and Slow Pointers Meet in the Cycle
Step 1 of 4
- Where slow and fast stand
- Where they meet
Both pointers start at the head, 3. The tail -4 links back to 2, so following next never reaches null.
All 4 steps as text
- Both pointers start at the head,
3. The tail-4links back to2, so followingnextnever reachesnull. slowmoves one node to2andfasttwo to0. Both are inside the cycle now, andfastis2nodes behindslow.slowmoves one node to0andfasttwo to2.fastgains one node per step, so the gap drops from2to1. It can't jump overslow.slowmoves to-4andfastlands there too, closing the last gap. They're on the same node, so the list has a cycle: returntrue.
Time O(n)Space O(1)
from __future__ import annotations
class ListNode:
"""One node of a singly linked list."""
def __init__(self, val: int, next: ListNode | None = None):
self.val = val
self.next = next
def has_cycle(head: ListNode | None) -> bool:
"""Whether following next pointers from head ever comes back to a node it has
already passed. Floyd's fast and slow pointers use O(1) extra space."""
slow = head
fast = head
while fast is not None and fast.next is not None:
slow = slow.next # One step. slow trails fast, so it isn't None here.
fast = fast.next.next # Two steps.
# In a cycle, fast closes the gap by one node per step, so it can't skip slow.
if slow is fast:
return True
return False # fast reached the end, so there's no cycle.
def build_with_cycle(values: list[int], pos: int) -> ListNode | None:
"""Builds a list from values. If pos isn't -1, the tail links back to the node
at index pos, which makes a cycle."""
nodes = [ListNode(value) for value in values]
for i in range(len(nodes) - 1):
nodes[i].next = nodes[i + 1]
if pos >= 0:
nodes[-1].next = nodes[pos] # Close the loop.
return nodes[0] if nodes else None
if __name__ == "__main__":
print(has_cycle(build_with_cycle([3, 2, 0, -4], 1))) # True
print(has_cycle(build_with_cycle([1, 2], 0))) # True
print(has_cycle(build_with_cycle([1], -1))) # False
print(has_cycle(build_with_cycle([], -1))) # False// One node of a singly linked list.
export class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
// Whether following next pointers from head ever comes back to a node it has
// already passed. Floyd's fast and slow pointers use O(1) extra space.
export function hasCycle(head: ListNode | null): boolean {
let slow = head;
let fast = head;
while (fast !== null && fast.next !== null) {
slow = slow!.next; // One step. slow trails fast, so it isn't null here.
fast = fast.next.next; // Two steps.
// In a cycle, fast closes the gap by one node per step, so it can't skip slow.
if (slow === fast) return true;
}
return false; // fast reached the end, so there's no cycle.
}
// Builds a list from values. If pos isn't -1, the tail links back to the node
// at index pos, which makes a cycle.
function buildWithCycle(values: number[], pos: number): ListNode | null {
const nodes = values.map((value) => new ListNode(value));
for (let i = 0; i + 1 < nodes.length; i++) nodes[i].next = nodes[i + 1];
if (pos >= 0) nodes[nodes.length - 1].next = nodes[pos]; // Close the loop.
return nodes.length > 0 ? nodes[0] : null;
}
console.log(hasCycle(buildWithCycle([3, 2, 0, -4], 1))); // true
console.log(hasCycle(buildWithCycle([1, 2], 0))); // true
console.log(hasCycle(buildWithCycle([1], -1))); // false
console.log(hasCycle(buildWithCycle([], -1))); // falsepackage main
import "fmt"
// ListNode is one node of a singly linked list.
type ListNode struct {
Val int
Next *ListNode
}
// hasCycle reports whether following Next pointers from head ever comes back
// to a node it has already passed. Floyd's fast and slow pointers use O(1)
// extra space.
func hasCycle(head *ListNode) bool {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next // One step. slow trails fast, so it isn't nil here.
fast = fast.Next.Next // Two steps.
// In a cycle, fast closes the gap by one node per step, so it can't skip slow.
if slow == fast {
return true
}
}
return false // fast reached the end, so there's no cycle.
}
// buildWithCycle builds a list from values. If pos isn't -1, the tail links
// back to the node at index pos, which makes a cycle.
func buildWithCycle(values []int, pos int) *ListNode {
nodes := make([]*ListNode, len(values))
for i, value := range values {
nodes[i] = &ListNode{Val: value}
}
for i := 0; i+1 < len(nodes); i++ {
nodes[i].Next = nodes[i+1]
}
if pos >= 0 {
nodes[len(nodes)-1].Next = nodes[pos] // Close the loop.
}
if len(nodes) == 0 {
return nil
}
return nodes[0]
}
func main() {
fmt.Println(hasCycle(buildWithCycle([]int{3, 2, 0, -4}, 1))) // true
fmt.Println(hasCycle(buildWithCycle([]int{1, 2}, 0))) // true
fmt.Println(hasCycle(buildWithCycle([]int{1}, -1))) // false
fmt.Println(hasCycle(buildWithCycle([]int{}, -1))) // false
}using System;
using System.Linq;
public class Solution
{
// One node of a singly linked list.
public class ListNode
{
public int Val;
public ListNode? Next;
public ListNode(int val, ListNode? next = null)
{
Val = val;
Next = next;
}
}
// Whether following Next pointers from head ever comes back to a node it has
// already passed. Floyd's fast and slow pointers use O(1) extra space.
public static bool HasCycle(ListNode? head)
{
var slow = head;
var fast = head;
while (fast != null && fast.Next != null)
{
slow = slow!.Next; // One step. slow trails fast, so it isn't null here.
fast = fast.Next.Next; // Two steps.
// In a cycle, fast closes the gap by one node per step, so it can't skip slow.
if (slow == fast) return true;
}
return false; // fast reached the end, so there's no cycle.
}
// Builds a list from values. If pos isn't -1, the tail links back to the node
// at index pos, which makes a cycle.
static ListNode? BuildWithCycle(int[] values, int pos)
{
var nodes = values.Select(value => new ListNode(value)).ToArray();
for (int i = 0; i + 1 < nodes.Length; i++) nodes[i].Next = nodes[i + 1];
if (pos >= 0) nodes[^1].Next = nodes[pos]; // Close the loop.
return nodes.Length > 0 ? nodes[0] : null;
}
public static void Main()
{
Console.WriteLine(HasCycle(BuildWithCycle(new[] { 3, 2, 0, -4 }, 1))); // True
Console.WriteLine(HasCycle(BuildWithCycle(new[] { 1, 2 }, 0))); // True
Console.WriteLine(HasCycle(BuildWithCycle(new[] { 1 }, -1))); // False
Console.WriteLine(HasCycle(BuildWithCycle(new int[0], -1))); // False
}
}public class Solution {
// One node of a singly linked list.
static class ListNode {
int val;
ListNode next;
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
// Whether following next pointers from head ever comes back to a node it has
// already passed. Floyd's fast and slow pointers use O(1) extra space.
static boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; // One step. slow trails fast, so it isn't null here.
fast = fast.next.next; // Two steps.
// In a cycle, fast closes the gap by one node per step, so it can't skip slow.
if (slow == fast) return true;
}
return false; // fast reached the end, so there's no cycle.
}
// Builds a list from values. If pos isn't -1, the tail links back to the node
// at index pos, which makes a cycle.
static ListNode buildWithCycle(int[] values, int pos) {
ListNode[] nodes = new ListNode[values.length];
for (int i = 0; i < values.length; i++) nodes[i] = new ListNode(values[i], null);
for (int i = 0; i + 1 < nodes.length; i++) nodes[i].next = nodes[i + 1];
if (pos >= 0) nodes[nodes.length - 1].next = nodes[pos]; // Close the loop.
return nodes.length > 0 ? nodes[0] : null;
}
public static void main(String[] args) {
System.out.println(hasCycle(buildWithCycle(new int[] {3, 2, 0, -4}, 1))); // true
System.out.println(hasCycle(buildWithCycle(new int[] {1, 2}, 0))); // true
System.out.println(hasCycle(buildWithCycle(new int[] {1}, -1))); // false
System.out.println(hasCycle(buildWithCycle(new int[] {}, -1))); // false
}
}Common Pitfalls
- Overwriting
curr.nextbefore saving it. In a reversal, store the next node first, or the rest of the list is gone. - Returning the old
headafter reversing. It's now the tail, and the new head isprev. - Following
fast.next.nextwithout checking thatfastandfast.nextexist. Any list without a cycle will hit null. - Special-casing the first node by hand instead of using a dummy, then getting the empty or one-node list wrong.
- Comparing node values instead of the nodes themselves in cycle detection. Two different nodes can hold the same value.
Practice
More problems to try on your own.