138. Copy List With Random Pointer - Explanation

Problem Link

Description

You are given the head of a linked list of length n. Unlike a singly linked list, each node contains an additional pointer random, which may point to any node in the list, or null.

Create a deep copy of the list.

The deep copy should consist of exactly n new nodes, each including:

  • The original value val of the copied node
  • A next pointer to the new node corresponding to the next pointer of the original node
  • A random pointer to the new node corresponding to the random pointer of the original node

Note: None of the pointers in the new list should point to nodes in the original list.

Return the head of the copied linked list.

In the examples, the linked list is represented as a list of n nodes. Each node is represented as a pair of [val, random_index] where random_index is the index of the node (0-indexed) that the random pointer points to, or null if it does not point to any node.

Example 1:

Input: head = [[3,null],[7,3],[4,0],[5,1]]

Output: [[3,null],[7,3],[4,0],[5,1]]

Example 2:

Input: head = [[1,null],[2,2],[3,2]]

Output: [[1,null],[2,2],[3,2]]

Constraints:

  • 0 <= n <= 100
  • -100 <= Node.val <= 100
  • random is null or is pointing to some node in the linked list.


Topics

Recommended Time & Space Complexity

You should aim for a solution as good or better than O(n) time and O(n) space, where n is the length of the given list.


Hint 1

There is an extra random pointer for each node, and unlike the next pointer, which points to the next node, the random pointer can point to any random node in the list. A deep copy is meant to create completely separate nodes occupying different memory. Why can't we build a new list while iterating through the original list?


Hint 2

Because, while iterating through the list, when we encounter a node and create a copy of it, we can't immediately assign the random pointer's address. This is because the random pointer might point to a node that has not yet been created. To solve this, we can first create copies of all the nodes in one iteration. However, we still can't directly assign the random pointers since we don't have the addresses of the copies of those random pointers. Can you think of a data structure to store this information? Maybe a hash data structure could help.


Hint 3

We can use a hash data structure, such as a hash map, which takes O(1) time to retrieve data. This can help by mapping the original nodes to their corresponding copies. This way, we can easily retrieve the copy of any node and assign the random pointers in a subsequent pass after creating copies of all nodes in the first pass.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Linked Lists - Understanding node structures with next pointers and traversal techniques
  • Hash Maps - Using dictionaries to map original nodes to their copies for O(1) lookup
  • Recursion - Building deep copies by recursively processing next and random pointers
  • Pointer Manipulation - Modifying node pointers to interleave or rearrange list structure

1. Recursion + Hash Map

Intuition

We must create a deep copy of a linked list where each node has both next and random pointers.
The main difficulty: multiple nodes may point to the same random node, so we must ensure each original node is copied exactly once.

A hash map helps us remember the copied version of each original node.
Using recursion, we:

  • copy the current node,
  • store it in the map,
  • recursively copy its next,
  • link its random using the map.

Algorithm

  1. If the current node is null, return null.
  2. If the node is already copied (exists in the map), return the stored copy.
  3. Create a new copy node and store it in the map.
  4. Recursively copy the next pointer.
  5. Set the random pointer using the map.
  6. Return the copied node.
"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def __init__(self):
        self.map = {}

    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if head is None:
            return None
        if head in self.map:
            return self.map[head]

        copy = Node(head.val)
        self.map[head] = copy
        copy.next = self.copyRandomList(head.next)
        copy.random = self.map.get(head.random)
        return copy

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

2. Hash Map (Two Pass)

Intuition

We want to copy a linked list where each node has both next and random pointers.
The challenge is that the random pointer can point anywhere — forward, backward, or even None.
So we must ensure every original node is copied exactly once, and all pointers are reconnected correctly.

A simple solution:

  • Pass 1: Create a copy of every node (just values), and store the mapping:
    original_node → copied_node
  • Pass 2: Use this map to connect next and random pointers for each copied node.

This guarantees all pointers are valid and no node is duplicated.

Algorithm

  1. Create a hash map oldToCopy, mapping each original node to its copied node.
    Include null -> null for convenience.
  2. First pass: iterate through the original list
    • Create a copy of each node.
    • Store the mapping in oldToCopy.
  3. Second pass: iterate again
    • Set copy.next using oldToCopy[original.next].
    • Set copy.random using oldToCopy[original.random].
  4. Return the copied version of the head using oldToCopy[head].
"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        oldToCopy = {None: None}

        cur = head
        while cur:
            copy = Node(cur.val)
            oldToCopy[cur] = copy
            cur = cur.next
        cur = head
        while cur:
            copy = oldToCopy[cur]
            copy.next = oldToCopy[cur.next]
            copy.random = oldToCopy[cur.random]
            cur = cur.next
        return oldToCopy[head]

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Hash Map (One Pass)

Intuition

We want to copy a linked list where every node has a next pointer and a random pointer.
Normally we need two passes: one to create nodes, and another to connect pointers.
But we can actually do both at the same time by using a hash map that automatically creates a copy node whenever we access it.

So when we say:

  • oldToCopy[cur] → gives the copied node
  • oldToCopy[cur.next] → gives the copy of cur.next (created if not present)
  • oldToCopy[cur.random] → gives the copy of cur.random

This lets us fill val, next, and random in one single pass.

Algorithm

  1. Create a hash map oldToCopy that returns a new empty node whenever we access a key that doesn't exist yet.
  2. Set oldToCopy[null] = null so next or random can safely point to null.
  3. Traverse the list once:
    • Set the copied node's value.
    • Link its next pointer using the map.
    • Link its random pointer using the map.
  4. Return the copied head from the hash map.
"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        oldToCopy = collections.defaultdict(lambda: Node(0))
        oldToCopy[None] = None

        cur = head
        while cur:
            oldToCopy[cur].val = cur.val
            oldToCopy[cur].next = oldToCopy[cur.next]
            oldToCopy[cur].random = oldToCopy[cur.random]
            cur = cur.next
        return oldToCopy[head]

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

4. Space Optimized - I

Intuition

We want to copy the list without using extra space like a hash map.
The trick is to interleave copied nodes inside the original list:

Original:
A → B → C

After interleaving:
A → A' → B → B' → C → C'

Now every copied node (A', B', C') is right next to the original node, so we can easily:

  • Set random of the copied node: A'.random = A.random.next
  • Finally separate the two lists again.

This gives a perfect clone using O(1) extra space.

Algorithm

  1. Interleave copy nodes

    • For each original node A, create a copy A' and insert it right after A.
  2. Assign random pointers

    • For each original node A, if A.random exists, then set A'.random = A.random.next.
  3. Unweave the lists

    • Restore the original list.
    • Extract all the copy nodes into a separate new list.
  4. Return the head of the copied list.

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if head is None:
            return None

        l1 = head
        while l1 is not None:
            l2 = Node(l1.val)
            l2.next = l1.next
            l1.next = l2
            l1 = l2.next

        newHead = head.next

        l1 = head
        while l1 is not None:
            if l1.random is not None:
                l1.next.random = l1.random.next
            l1 = l1.next.next

        l1 = head
        while l1 is not None:
            l2 = l1.next
            l1.next = l2.next
            if l2.next is not None:
                l2.next = l2.next.next
            l1 = l1.next

        return newHead

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(n)O(n) for the output.

5. Space Optimized - II

Intuition

This method also avoids extra space like a hash map, but instead of inserting copied nodes into the next pointer chain, we temporarily use the random pointer to store the copied nodes.

For every original node:

  • We create its copy and store it in the original node’s random pointer.
  • The copy’s next pointer initially points to whatever the original node’s random was pointing to.

This allows us to:

  1. Create all copies without extra memory.
  2. Fix the random pointers of the copied nodes using the original structure.
  3. Reconnect the next pointers to build the final deep-copy list.
  4. Restore the original list.

Everything happens using only pointer manipulations — no extra arrays, no hash map.

Algorithm

  1. Create all copied nodes

    • For each original node A, create a copy A'.
    • Store this copy inside A.random.
    • Set A'.next = A.random_old.
  2. Fix random pointers of copies

    • For each original node A, set A'.random = A.random_old.random' (if exists).
  3. Extract the copied list and restore original list

    • For each original node A:
      • Restore A.random = original_random.
      • Connect A'.next to the next copied node.
  4. Return the head of the copied list (which is head.random).

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if head is None:
            return None

        l1 = head
        while l1:
            l2 = Node(l1.val)
            l2.next = l1.random
            l1.random = l2
            l1 = l1.next

        newHead = head.random

        l1 = head
        while l1:
            l2 = l1.random
            l2.random = l2.next.random if l2.next else None
            l1 = l1.next

        l1 = head
        while l1 is not None:
            l2 = l1.random
            l1.random = l2.next
            l2.next = l1.next.random if l1.next else None
            l1 = l1.next

        return newHead

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(n)O(n) for the output.

Common Pitfalls

Creating Multiple Copies of the Same Node

Without a hash map to track already-copied nodes, you might create duplicate copies when multiple random pointers point to the same node. Always check if a node has been copied before creating a new copy.

# Wrong - creates duplicates
copy.random = Node(original.random.val)

# Correct - use hash map to reuse existing copy
copy.random = oldToCopy[original.random]

Forgetting to Handle Null Random Pointers

The random pointer can be null. Attempting to access properties of null causes crashes. Always check for null before dereferencing.

# Wrong - crashes if random is None
copy.random = oldToCopy[original.random]  # KeyError if random is None

# Correct - handle None explicitly
oldToCopy[None] = None  # or check before access

Not Restoring Original List in Space-Optimized Solutions

In the interleaving approach, failing to properly unweave the two lists corrupts the original list and may break the copied list's pointers. The separation step must correctly restore both next pointers.

# During separation, update both lists
l1.next = l2.next        # restore original
l2.next = l2.next.next   # link copies (check for null first)