Attic
DSALinked Lists

Canonical Problem Patterns

Merging sorted lists, LRU cache doubly-linked node unlinking, and deep copying lists with random pointers

This reference covers the most frequent linked list problems encountered in technical interviews, including multi-list merging, complex pointer interleaving, and designing cache backing stores.


1. Merge Two Sorted Lists

Combine two sorted linked lists into a single sorted list by dynamically splicing pointers:

MergeTwoLists.java
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (list1 != null && list2 != null) {
        if (list1.val <= list2.val) {
            tail.next = list1;
            list1 = list1.next;
        } else {
            tail.next = list2;
            list2 = list2.next;
        }
        tail = tail.next;
    }

    // Attach remaining non-null sublist directly in O(1)
    tail.next = (list1 != null) ? list1 : list2;

    return dummy.next;
}

Time & Space: O(m + n) time where m and n are the lengths of the two lists. Space is O(1) because original node objects are reused without new allocations.


2. Merge K Sorted Lists (Hard)

Combine k sorted lists into one sorted list:

Maintain a min-heap containing the current head of each list:

MergeKSortedListsHeap.java
public ListNode mergeKLists(ListNode[] lists) {
    if (lists == null || lists.length == 0) return null;

    // Min-heap ordered by node value
    PriorityQueue<ListNode> pq = new PriorityQueue<>(
        Comparator.comparingInt(node -> node.val)
    );

    // Add the head of each non-empty list
    for (ListNode list : lists) {
        if (list != null) pq.offer(list);
    }

    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (!pq.isEmpty()) {
        ListNode smallest = pq.poll();
        tail.next = smallest;
        tail = tail.next;

        // If the extracted node has a successor, push it to heap
        if (smallest.next != null) {
            pq.offer(smallest.next);
        }
    }

    return dummy.next;
}

Complexity: O(N log k) time (where N is total nodes across all lists) and O(k) heap memory.


3. Copy List with Random Pointer

Each node contains an additional random pointer that could point to any node in the list or null. A deep copy can be created in O(n) time and O(1) extra space using 3-phase node interleaving:

CopyRandomList.java
class Node {
    int val;
    Node next;
    Node random;
    public Node(int val) { this.val = val; }
}

public class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;

        // Phase 1: Create cloned nodes interleaved right after original nodes
        Node curr = head;
        while (curr != null) {
            Node clone = new Node(curr.val);
            clone.next = curr.next;
            curr.next = clone;
            curr = clone.next;
        }

        // Phase 2: Copy random pointers
        curr = head;
        while (curr != null) {
            if (curr.random != null) {
                curr.next.random = curr.random.next;
            }
            curr = curr.next.next;
        }

        // Phase 3: Unweave and separate the two lists
        Node dummy = new Node(0);
        Node copyCurr = dummy;
        curr = head;

        while (curr != null) {
            Node clone = curr.next;
            curr.next = clone.next; // Restore original list
            copyCurr.next = clone;  // Build cloned list

            copyCurr = copyCurr.next;
            curr = curr.next;
        }

        return dummy.next;
    }
}

4. Doubly-Linked Node Unlinking (LRU Cache Engine)

A custom doubly-linked list with sentinel head and tail nodes allows O(1) constant time unlinking and insertion, forming the backbone of LRU eviction caches:

DLinkedListLRU.java
public class DoubleList {
    private static class Node {
        int key, val;
        Node prev, next;
        Node(int k, int v) { key = k; val = v; }
    }

    private final Node head = new Node(0, 0); // Sentinel head
    private final Node tail = new Node(0, 0); // Sentinel tail

    public DoubleList() {
        head.next = tail;
        tail.prev = head;
    }

    // Add node right after sentinel head (most recently used): O(1)
    public void addFirst(Node node) {
        node.next = head.next;
        node.prev = head;
        head.next.prev = node;
        head.next = node;
    }

    // Remove an existing node: O(1)
    public void remove(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // Remove the least recently used node (before sentinel tail): O(1)
    public Node removeLast() {
        if (tail.prev == head) return null;
        Node lru = tail.prev;
        remove(lru);
        return lru;
    }
}

5. Partition List Around Value x

Partition a linked list such that all nodes with value strictly less than x appear before nodes with value >= x, preserving their original relative order:

PartitionList.java
public ListNode partition(ListNode head, int x) {
    ListNode smallerHead = new ListNode(0);
    ListNode greaterHead = new ListNode(0);

    ListNode small = smallerHead;
    ListNode great = greaterHead;

    while (head != null) {
        if (head.val < x) {
            small.next = head;
            small = small.next;
        } else {
            great.next = head;
            great = great.next;
        }
        head = head.next;
    }

    great.next = null; // Sever trailing pointer
    small.next = greaterHead.next; // Splice both sublists

    return smallerHead.next;
}

Problem Archetype Cheat Sheet

Problem PatternCore StrategyTime ComplexityAuxiliary Space
Middle NodeFast & Slow pointers (fast.next.next)O(n)O(1)
Cycle DetectionFloyd's algorithm (slow == fast)O(n)O(1)
Cycle Entry PointAdvance ptr1 from head and ptr2 from meeting pointO(n)O(1)
Remove N-th from EndAdvance fast N + 1 steps, then both simultaneouslyO(n)O(1)
Reverse SublistSplicing with 3 pointers (prev, curr, next)O(n)O(1)
Palindrome VerificationMiddle -> Reverse 2nd half -> CompareO(n)O(1)
Merge K ListsPriorityQueue (Min-heap) or Divide & ConquerO(N log k)O(k) or O(1)
Copy Random PointersInterleave clones -> copy random -> unweaveO(n)O(1)
LRU Cache EvictionDoubly-linked list with sentinel head and tailO(1)O(1)

On this page