Skip to main content

Command Palette

Search for a command to run...

The Everyday Coder’s Guide to Advanced Linked Lists

Published
7 min read
The Everyday Coder’s Guide to Advanced Linked Lists

Linked Lists are the introverts of the data structure world. They don’t live in a big, loud, contiguous block of memory like Arrays. Instead, each Node is a quiet loner that just holds some data and a pointer to its one best friend (the next Node).

But once you move past "insert" and "delete," the interview questions get weird. We’re going to deep-dive into four patterns that look scary but are just logic puzzles. Grab a coffee.


1. The Detective Work: Floyd’s Cycle Detection (Tortoise and Hare)

The Scenario: You’re walking through a linked list, and you suspect it might not be a straight line. It might be a circle—a corrupt list where a node points back to an earlier one, trapping you in an infinite loop.

You can’t just mark every Node you’ve visited as "seen" because you might not be allowed to modify the list.

The Aha Moment: Imagine an old-school running track. In Lane 1, you have a slow, steady walker (the Tortoise). In Lane 2, you have an Olympic sprinter (the Hare). If the track is a loop, no matter where they start, the fast runner will eventually lap the slow runner and they’ll be at the same spot at the same time.

If the track is a dead-end road, the fast runner will just hit the end and stop.

The Algorithm:

  1. slow pointer moves 1 step.

  2. fast pointer moves 2 steps.

  3. If fast ever finds null, no cycle.

  4. If slow == fast, a cycle exists.

The Code (Python):

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

Time to Solve: 5 minutes to understand, 5 minutes to code.


2. The Reversal: Turning the Train Around (Iterative + Recursive)

Reversing a linked list is the "Hello World" of pointer manipulation. You’re basically asking everyone in a line to turn around and face the opposite direction.

The Iterative Way (The Real-World Way)

You don't move the people; you just change who they are looking at.

The Pointer Tango: You need three pointers: prev (the person behind you), curr (you), and nxt (the person in front of you).

  1. Save who is in front of you (nxt = curr.next) or you’ll lose them.

  2. Turn around and face backward (curr.next = prev).

  3. Everyone shuffles forward one spot.

The Code:

def reverse_iterative(head):
    prev = None
    curr = head
    while curr:
        nxt = curr.next  # Save the rest of the list
        curr.next = prev # Reverse the link
        prev = curr      # Move prev up
        curr = nxt       # Move curr up
    return prev

The Recursive Way (The Elegant Magic Trick)

Recursion is tricky here. You have to trust that the function works for the rest of the list.

The Logic: If you have 1 -> 2 -> 3 -> 4, and you magically reverse everything after 1, you get 1 -> 2 <- 3 <- 4. To put 1 at the end, you just need 2 (which is head.next) to point back to 1, and 1 to point to None.

The Code:

def reverse_recursive(head):
    if not head or not head.next:
        return head
    
    reversed_list_head = reverse_recursive(head.next)
    
    # The magic: head.next is now the LAST node of the reversed part
    head.next.next = head
    head.next = None
    
    return reversed_list_head

Time to Solve: 10 minutes to sketch, 10 minutes for both codes.


3. The Integrator: Merge K Sorted Lists using Min-Heap

The Scenario: You have K sorted lists, and you want to merge them into one giant sorted list. The brute-force way is to merge list 1 and 2, then merge the result with 3, and so on. That’s slow.

The Aha Moment: Think of a sports draft. The manager of each team (each list) sends their top-scoring player (the head node) to the waiting room. You (the commissioner) look at all the players in the waiting room, pick the absolute smallest one, and put them on the field (the final list). As soon as you pick a player, their team manager sends their next best player to the waiting room.

This waiting room is a Min-Heap. It always keeps the smallest element at the top for you to grab instantly.

The Algorithm:

  1. Put the head of every list into the Min-Heap.

  2. Pop the smallest node. This is your "next" node in the sorted answer.

  3. Push the next node from that popped node’s original list into the Heap.

  4. Repeat until the heap is empty.

The Code (Python - using heapq):

import heapq

def merge_k_lists(lists):
    heap = []
    
    # Step 1: Load the waiting room
    for i, node in enumerate(lists):
        if node:
            # We use i as a tie-breaker if two nodes have the same value
            heapq.heappush(heap, (node.val, i, node))
    
    dummy = ListNode(-1)
    tail = dummy
    
    # Step 2 & 3: The Draft
    while heap:
        val, i, node = heapq.heappop(heap)
        tail.next = node
        tail = tail.next
        
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
            
    return dummy.next

Time to Solve: 15 minutes to understand the metaphor, 10 minutes to code.


4. The Memory Wizard: LRU Cache (HashMap + Doubly Linked List)

The Scenario: You’re building a cache with limited space. When it’s full, you must kick out the "Least Recently Used" item (LRU). You need get(key) and put(key, value) to be instant (O(1)).

This is the king of Linked List questions because it requires marrying a Hash Map with a Doubly Linked List.

  • HashMap: Instantly finds the node by its key. (Like a contact list telling you exactly where your friend "key" is sitting).

  • Doubly Linked List: Manages the "recency" order.

The Structure: You keep a line of seats. The leftmost seat is Most Recently Used, the rightmost seat is Least Recently Used.

  1. Get: Look up the node in the HashMap. Move it from its current seat straight to the VIP spot (the head).

  2. Put: If new, put it at the head. If the room is full, kick out the tail (LRU).

The Crucial Trick: To avoid dealing with "remove from middle" edge cases involving null, we use Dummy Nodes (head and tail). The real data lives between them.

The Code (Python):

class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity):
        self.cache = {}
        self.capacity = capacity
        # Dummy Heads/Tails
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove_node(self, node):
        # Pluck it out
        prev = node.prev
        nxt = node.next
        prev.next = nxt
        nxt.prev = prev

    def _add_to_head(self, node):
        # Put it right after the dummy head
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

    def get(self, key):
        if key in self.cache:
            node = self.cache[key]
            self._remove_node(node)
            self._add_to_head(node)
            return node.value
        return -1

    def put(self, key, value):
        if key in self.cache:
            self._remove_node(self.cache[key])
        node = DLinkedNode(key, value)
        self._add_to_head(node)
        self.cache[key] = node
        
        if len(self.cache) > self.capacity:
            lru = self.tail.prev
            self._remove_node(lru)
            del self.cache[lru.key]

Time to Solve: 20 minutes of staring at the pointer manipulation, 10 minutes to type it out without looking.