foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • quiz
Data Structures
  • Understand doubly linked lists for O(1) deletion with node pointer
  • See LRU cache implementation combining hash map and doubly linked list
  • Learn palindrome check and list reordering with slow/fast pointers

Advanced Linked List Concepts

Basic operations cover most needs. Now let's tackle advanced patterns and problems.

Doubly Linked Lists

Each node has prev and next pointers.

class DoublyNode:
    def __init__(self, data):
        self.data = data
        self.next = None
        self.prev = None
null ← [A] ↔ [B] ↔ [C] → null

Advantages:

  • Traverse both directions
  • Delete a node in O(1) if you have pointer to it (no need to find predecessor)
  • Easier to implement certain operations

Disadvantage: Extra memory for prev pointers.

Insert after a node:

def insertAfter(node, data):
    new_node = DoublyNode(data)
    new_node.next = node.next
    new_node.prev = node
    
    if node.next is not None:
        node.next.prev = new_node
    
    node.next = new_node

Delete a node (given pointer to it):

def deleteNode(node):
    if node.prev is not None:
        node.prev.next = node.next
    if node.next is not None:
        node.next.prev = node.prev

O(1). No traversal needed.

Circular Linked Lists

Last node points back to first instead of null.

[A] → [B] → [C] → (back to A)

Use case: Round-robin scheduling, circular buffers, multiplayer turn-based games.

Traverse (be careful not to loop forever):

def traverse(head):
    if head is None:
        return
    
    current = head
    while True:
        print(current.data)
        current = current.next
        if current == head:
            break

Insert at end:

def insertAtEnd(head, data):
    new_node = Node(data)
    
    if head is None:
        new_node.next = new_node  # Points to itself
        return new_node
    
    current = head
    while current.next != head:
        current = current.next
    
    current.next = new_node
    new_node.next = head
    return head

Skip Lists

Linked list with multiple levels of pointers for faster search.

Level 2: A ---------> C ---------> E
Level 1: A ----> B -> C ----> D -> E
Level 0: A -> B -> C -> D -> E

Search starts at highest level, skips nodes, drops down when needed. Average O(log n) search instead of O(n).

Think of it as a linked list with "express lanes." Complex to implement, rarely used (balanced trees are more common), but elegant.

Intersection of Two Lists

Find the node where two linked lists intersect.

List 1: A → B → C ↘
                   E → F → G
List 2: D -----→ /

Both lists share E → F → G.

Approach: Find lengths, advance longer list by difference, then move both together until they meet.

def getIntersection(head1, head2):
    len1 = getLength(head1)
    len2 = getLength(head2)
    
    # Advance longer list
    diff = abs(len1 - len2)
    if len1 > len2:
        for _ in range(diff):
            head1 = head1.next
    else:
        for _ in range(diff):
            head2 = head2.next
    
    # Move together
    while head1 is not None and head2 is not None:
        if head1 == head2:
            return head1  # Intersection node
        head1 = head1.next
        head2 = head2.next
    
    return None  # No intersection

O(n + m).

Palindrome Check

Check if a linked list is a palindrome (same forwards and backwards).

Approach:

  1. Find middle (slow/fast)
  2. Reverse second half
  3. Compare first half and reversed second half
def isPalindrome(head):
    if head is None or head.next is None:
        return True
    
    # Find middle
    slow = fast = head
    while fast.next is not None and fast.next.next is not None:
        slow = slow.next
        fast = fast.next.next
    
    # Reverse second half
    second_half = reverse(slow.next)
    
    # Compare
    first = head
    second = second_half
    is_palin = True
    
    while second is not None:
        if first.data != second.data:
            is_palin = False
            break
        first = first.next
        second = second.next
    
    # Optional: restore list by reversing second half again
    slow.next = reverse(second_half)
    
    return is_palin

O(n) time, O(1) space.

Flatten a Multilevel List

Linked list nodes have a child pointer in addition to next.

1 → 2 → 3 → 4
    ↓
    5 → 6
    ↓
    7 → 8

Flatten to: 1 → 2 → 5 → 7 → 8 → 6 → 3 → 4

Use a stack or recursion to traverse depth-first.

def flatten(head):
    if head is None:
        return None
    
    stack = [head]
    prev = None
    
    while stack:
        current = stack.pop()
        
        if prev is not None:
            prev.next = current
            current.prev = prev
        
        if current.next is not None:
            stack.append(current.next)
        if current.child is not None:
            stack.append(current.child)
            current.child = None
        
        prev = current
    
    return head

O(n) where n is total nodes.

LRU Cache with Linked List

Least Recently Used cache: evict least recently used item when full.

Use doubly linked list + hash map:

  • Hash map: key → node (O(1) access)
  • Doubly linked list: maintain usage order (most recent at head)
class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = {}  # key → node
        self.head = DoublyNode(0, 0)  # Dummy head
        self.tail = DoublyNode(0, 0)  # Dummy tail
        self.head.next = self.tail
        self.tail.prev = self.head
    
    def get(self, key):
        if key not in self.cache:
            return -1
        
        node = self.cache[key]
        self._remove(node)
        self._add_to_head(node)
        return node.value
    
    def put(self, key, value):
        if key in self.cache:
            self._remove(self.cache[key])
        
        node = DoublyNode(key, value)
        self.cache[key] = node
        self._add_to_head(node)
        
        if len(self.cache) > self.capacity:
            lru = self.tail.prev
            self._remove(lru)
            del self.cache[lru.key]
    
    def _remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _add_to_head(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

O(1) for get and put. Hash map gives instant access, doubly linked list gives O(1) add/remove.

Sorting a Linked List

Merge sort works well for linked lists.

def mergeSort(head):
    if head is None or head.next is None:
        return head
    
    # Find middle
    slow = fast = head
    prev = None
    
    while fast is not None and fast.next is not None:
        prev = slow
        slow = slow.next
        fast = fast.next.next
    
    # Split
    prev.next = None
    
    # Sort halves
    left = mergeSort(head)
    right = mergeSort(slow)
    
    # Merge
    return mergeSorted(left, right)

O(n log n) time, O(log n) space for recursion stack.

Quicksort is harder for linked lists (no random access for pivot selection).

Reorder List

Given: L0 → L1 → L2 → ... → Ln-1 → Ln

Reorder to: L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ...

Example: 1 → 2 → 3 → 4 → 5 becomes 1 → 5 → 2 → 4 → 3

Approach:

  1. Find middle
  2. Reverse second half
  3. Merge two halves alternately
def reorderList(head):
    if head is None or head.next is None:
        return
    
    # Find middle
    slow = fast = head
    while fast.next is not None and fast.next.next is not None:
        slow = slow.next
        fast = fast.next.next
    
    # Reverse second half
    second = reverse(slow.next)
    slow.next = None
    
    # Merge alternately
    first = head
    while second is not None:
        temp1 = first.next
        temp2 = second.next
        
        first.next = second
        second.next = temp1
        
        first = temp1
        second = temp2

O(n).

XOR Linked List

Memory-efficient doubly linked list using XOR.

Each node stores prev XOR next instead of separate pointers.

# Conceptual (not directly implementable in Python due to pointer arithmetic)
class XORNode:
    def __init__(self, data):
        self.data = data
        self.npx = 0  # XOR of prev and next addresses

To traverse: if you know prev, you can compute next via next = prev XOR npx.

Saves memory but harder to implement and debug. Rarely used in practice.

The Takeaway

Advanced linked list techniques:

  • Doubly linked: O(1) delete with node pointer
  • Circular: No end, useful for round-robin
  • Skip lists: Multi-level for O(log n) search
  • Two-pointer tricks: Intersection, palindrome, reordering
  • Combine with hash maps: LRU cache in O(1)

Understanding these means recognizing:

  • When extra pointers (prev, child) simplify problems
  • How to use slow/fast pointers creatively
  • When linked lists integrate with other structures (hash maps, stacks)

Master these, and you'll solve complex linked list problems efficiently.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service