Skip to content

Reverse, Cycle & Merge Sort Linked List

Fundamental linked list manipulations


Visual Guides

Linked List Reversal

List Reversal

Floyd's Cycle Detection

Cycle Detection


Reverse Linked List

Reversing a linked list is one of the most fundamental problems in data structures (LeetCode #206). It tests your understanding of pointer manipulation and is a building block for many advanced linked list operations.

Iterative Solution

The iterative approach uses three pointers to reverse links in place:

python
def reverseList(head):
    prev, curr = None, head

    while curr:
        next_temp = curr.next  # Save next node before breaking link
        curr.next = prev       # Reverse the link
        prev = curr            # Move prev forward
        curr = next_temp       # Move curr forward

    return prev
java
public ListNode reverseList(ListNode head) {
    ListNode prev = null, curr = head;

    while (curr != null) {
        ListNode nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }

    return prev;
}

Complexity: Time O(n) · Space O(1)

  • Time: O(n) because we traverse each node exactly once, performing constant-time pointer operations at each step
  • Space: O(1) because we only use three pointers (prev, curr, next_temp) regardless of list size

Key Insight: Always save curr.next before modifying it, or you'll lose the rest of the list!

Recursive Solution

The recursive approach reverses the rest of the list first, then fixes the current node's pointer:

python
def reverseList_recursive(head):
    # Base case: empty list or single node
    if not head or not head.next:
        return head

    # Reverse the rest of the list
    new_head = reverseList_recursive(head.next)

    # Fix pointers for current node
    head.next.next = head  # Make next node point back to current
    head.next = None       # Break original forward link

    return new_head

Complexity: Time O(n) · Space O(n)

  • Time: O(n) because each node is visited exactly once during the recursive calls
  • Space: O(n) because the recursion creates n stack frames, one for each node in the list

How it works: For list 1 -> 2 -> 3, after recursion returns with new_head = 3:

  • At node 2: 2.next.next = 2 makes 3 -> 2, then 2.next = None
  • At node 1: 1.next.next = 1 makes 2 -> 1, then 1.next = None

Mermaid Diagram

Visual Step-by-Step:

Original: 1 -> 2 -> 3 -> null
          ^
         curr (prev = null)

Step 1:   null <- 1    2 -> 3 -> null
                 ^     ^
               prev   curr

Step 2:   null <- 1 <- 2    3 -> null
                       ^     ^
                     prev   curr

Step 3:   null <- 1 <- 2 <- 3
                            ^
                          prev (curr = null, exit loop)

Return: prev (node 3)

Complexity

ApproachTimeSpace
IterativeO(n)O(1)
RecursiveO(n)O(n) - call stack

Linked List Cycle

Floyd's Cycle Detection Algorithm (also known as the "Tortoise and Hare" algorithm) is an elegant solution for detecting cycles in linked lists.

Detect Cycle (LeetCode #141)

python
def hasCycle(head) -> bool:
    slow = fast = head

    while fast and fast.next:
        slow = slow.next        # Move 1 step
        fast = fast.next.next   # Move 2 steps
        if slow == fast:
            return True

    return False
java
public boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;

    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }

    return false;
}

Complexity: Time O(n) · Space O(1)

  • Time: O(n) because in the worst case (no cycle), fast pointer traverses n/2 nodes; with a cycle, pointers meet within n iterations
  • Space: O(1) because we only use two pointers (slow, fast) regardless of list size

Intuition: If there's a cycle, the fast pointer will eventually "lap" the slow pointer. If no cycle exists, fast will reach the end.

Find Cycle Start (LeetCode #142)

python
def detectCycle(head):
    slow = fast = head

    # Phase 1: Detect cycle
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    else:
        return None  # No cycle found

    # Phase 2: Find entrance
    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next

    return slow  # This is the cycle start
java
public ListNode detectCycle(ListNode head) {
    ListNode slow = head, fast = head;
    boolean hasCycle = false;

    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) { hasCycle = true; break; }
    }

    if (!hasCycle) return null;

    slow = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }

    return slow;
}

Complexity: Time O(n) · Space O(1)

  • Time: O(n) because phase 1 takes O(n) to detect cycle, and phase 2 takes at most O(n) to find the cycle start
  • Space: O(1) because we only use two pointers (slow, fast) throughout both phases

Why This Works (Mathematical Proof)

Let's define:

  • F = distance from head to cycle start
  • C = cycle length
  • a = distance from cycle start to meeting point
Head ----F---- Cycle Start ----a---- Meeting Point
                    |                      |
                    +--- (C - a) ----------+

When slow and fast meet:

  • Slow has traveled: F + a
  • Fast has traveled: F + a + nC (where n >= 1 cycles)

Since fast moves twice as fast:

2(F + a) = F + a + nC
F + a = nC
F = nC - a
F = (n-1)C + (C - a)

Key insight: The distance from head to cycle start (F) equals the distance from meeting point to cycle start going forward (C - a) plus some complete cycles.

Therefore, if we move one pointer from head and another from meeting point (both at speed 1), they will meet at the cycle start!

Edge Cases

python
def detectCycle_robust(head):
    # Handle empty list or single node
    if not head or not head.next:
        return None

    slow = fast = head
    has_cycle = False

    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            has_cycle = True
            break

    if not has_cycle:
        return None

    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next

    return slow

Complexity: Time O(n) · Space O(1)

  • Time: O(n) because we traverse the list twice at most (once for detection, once for finding start)
  • Space: O(1) because we use only a constant number of pointers regardless of list size

Merge Sort Doubly Linked List

Merge sort is ideal for linked lists because it doesn't require random access and achieves O(n log n) time complexity.

Approach

  1. Split: Find the middle of the list and split it into two halves
  2. Recursively sort: Apply merge sort to each half
  3. Merge: Combine the sorted halves maintaining sorted order

DLL Node Definition

python
class DLLNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.prev = prev
        self.next = next

Solution

python
def sortDLL(head):
    """Sort a doubly linked list using merge sort."""
    # Base case: empty or single node
    if not head or not head.next:
        return head

    # Split list at middle
    middle = get_middle(head)
    second = middle.next
    middle.next = None
    if second:
        second.prev = None

    # Recursively sort both halves
    left = sortDLL(head)
    right = sortDLL(second)

    # Merge sorted halves
    return merge(left, right)


def get_middle(head):
    """Find the middle node using slow/fast pointers."""
    slow = fast = head
    while fast.next and fast.next.next:
        slow = slow.next
        fast = fast.next.next
    return slow


def merge(left, right):
    """Merge two sorted doubly linked lists."""
    dummy = DLLNode(0)
    curr = dummy

    while left and right:
        if left.val <= right.val:
            curr.next = left
            left.prev = curr
            left = left.next
        else:
            curr.next = right
            right.prev = curr
            right = right.next
        curr = curr.next

    # Attach remaining nodes
    if left:
        curr.next = left
        left.prev = curr
    if right:
        curr.next = right
        right.prev = curr

    # Fix head's prev pointer
    result = dummy.next
    if result:
        result.prev = None
    return result
java
static class DLLNode {
    int val;
    DLLNode prev, next;
    DLLNode(int val) { this.val = val; }
}

public DLLNode sortDLL(DLLNode head) {
    if (head == null || head.next == null) return head;

    DLLNode slow = head, fast = head;
    while (fast.next != null && fast.next.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }

    DLLNode second = slow.next;
    slow.next = null;
    if (second != null) second.prev = null;

    DLLNode left = sortDLL(head);
    DLLNode right = sortDLL(second);

    return mergeDLL(left, right);
}

private DLLNode mergeDLL(DLLNode l1, DLLNode l2) {
    DLLNode dummy = new DLLNode(0);
    DLLNode curr = dummy;

    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            curr.next = l1; l1.prev = curr; l1 = l1.next;
        } else {
            curr.next = l2; l2.prev = curr; l2 = l2.next;
        }
        curr = curr.next;
    }

    if (l1 != null) { curr.next = l1; l1.prev = curr; }
    if (l2 != null) { curr.next = l2; l2.prev = curr; }

    DLLNode result = dummy.next;
    if (result != null) result.prev = null;
    return result;
}

Complexity: Time O(n log n) · Space O(log n)

  • Time: O(n log n) because we perform log n levels of splitting/merging, and each level processes all n nodes
  • Space: O(log n) for the recursion stack depth; the merge operation itself is O(1) as we only rearrange pointers

Merge Sort for Singly Linked List (LeetCode #148)

python
def sortList(head):
    """Sort a singly linked list using merge sort."""
    if not head or not head.next:
        return head

    # Find middle using slow/fast pointers
    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

    # Split the list
    mid = slow.next
    slow.next = None

    # Recursively sort and merge
    left = sortList(head)
    right = sortList(mid)
    return mergeLists(left, right)


def mergeLists(l1, l2):
    """Merge two sorted linked lists."""
    dummy = ListNode(0)
    curr = dummy

    while l1 and l2:
        if l1.val <= l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        curr = curr.next

    curr.next = l1 or l2
    return dummy.next

Complexity: Time O(n log n) · Space O(log n)

  • Time: O(n log n) because merge sort divides the list log n times and merges all n elements at each level
  • Space: O(log n) for the recursion stack; unlike array merge sort, no O(n) auxiliary array is needed

Visualization

Complexity

MetricValueExplanation
TimeO(n log n)log n levels of recursion, O(n) work per level
SpaceO(log n)Recursion stack depth

Note: Unlike array-based merge sort, linked list merge sort doesn't need O(n) extra space for merging since we just rearrange pointers.


Interview Applications

Common Interview Patterns

  1. Reverse in Groups (LeetCode #25): Reverse nodes in k-group

    python
    def reverseKGroup(head, k):
        # Count nodes
        count = 0
        curr = head
        while curr and count < k:
            curr = curr.next
            count += 1
    
        if count < k:
            return head
    
        # Reverse k nodes
        prev, curr = None, head
        for _ in range(k):
            next_temp = curr.next
            curr.next = prev
            prev = curr
            curr = next_temp
    
        # Recursively process rest
        head.next = reverseKGroup(curr, k)
        return prev
  2. Palindrome Linked List (LeetCode #234): Uses reversal + slow-fast

    python
    def isPalindrome(head):
        # Find middle
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
    
        # Reverse second half
        prev = None
        while slow:
            next_temp = slow.next
            slow.next = prev
            prev = slow
            slow = next_temp
    
        # Compare halves
        left, right = head, prev
        while right:
            if left.val != right.val:
                return False
            left = left.next
            right = right.next
        return True

    Complexity: Time O(n) · Space O(1)

    • Time: O(n) because we traverse the list 1.5 times: once to find middle/reverse, once to compare halves
    • Space: O(1) because we reverse the second half in-place using only pointer variables
  3. Reorder List (LeetCode #143): Combines finding middle, reversal, and merging

    python
    def reorderList(head):
        if not head or not head.next:
            return
    
        # Find middle
        slow, fast = head, head
        while fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
    
        # Reverse second half
        prev, curr = None, slow.next
        slow.next = None
        while curr:
            next_temp = curr.next
            curr.next = prev
            prev = curr
            curr = next_temp
    
        # Merge alternating
        first, second = head, prev
        while second:
            tmp1, tmp2 = first.next, second.next
            first.next = second
            second.next = tmp1
            first, second = tmp1, tmp2

    Complexity: Time O(n) · Space O(1)

    • Time: O(n) because we traverse the list three times: find middle, reverse second half, merge alternating
    • Space: O(1) because all operations are done in-place with only pointer manipulation

Interview Tips

TopicKey Points
ReversalAlways save next pointer before modifying; handle null cases
Cycle DetectionFloyd's algorithm is O(1) space; understand the math for finding start
Merge SortPreferred for linked lists; no random access needed; stable sort
Edge CasesEmpty list, single node, even/odd length
ProblemTechnique
#206 Reverse Linked ListBasic reversal
#92 Reverse Linked List IIPartial reversal
#25 Reverse Nodes in k-GroupGroup reversal
#141 Linked List CycleCycle detection
#142 Linked List Cycle IIFind cycle start
#148 Sort ListMerge sort
#21 Merge Two Sorted ListsMerge operation
#23 Merge k Sorted ListsAdvanced merging
#234 Palindrome Linked ListReversal + comparison
#143 Reorder ListMultiple techniques

Summary

OperationTimeSpaceKey Insight
Reverse (Iterative)O(n)O(1)Three pointers, save next before modifying
Reverse (Recursive)O(n)O(n)Reverse rest, then fix current node
Cycle DetectionO(n)O(1)Fast catches slow if cycle exists
Find Cycle StartO(n)O(1)Distance math: F = (n-1)C + (C-a)
Merge SortO(n log n)O(log n)Split, recurse, merge

Sources