title: Copy List with Random Pointer
slug: copy-list-with-random-pointer
difficulty: medium
leetcode_id: 138
leetcode_url: https://leetcode.com/problems/copy-list-with-random-pointer/
categories:
  - linked-lists
  - hash-tables
patterns:
  - slug: linkedlist-reversal
    is_optimal: true

function_signature: "def copy_random_list(head: Node | None) -> Node | None:"

test_cases:
  visible:
    - input: { head: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]] }
      expected: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]
    - input: { head: [[1, 1], [2, 1]] }
      expected: [[1, 1], [2, 1]]
    - input: { head: [[3, null], [3, 0], [3, null]] }
      expected: [[3, null], [3, 0], [3, null]]
  hidden:
    - input: { head: [] }
      expected: []
    - input: { head: [[1, null]] }
      expected: [[1, null]]
    - input: { head: [[5, 0]] }
      expected: [[5, 0]]
    - input: { head: [[1, 1], [2, 0]] }
      expected: [[1, 1], [2, 0]]
    - input: { head: [[1, null], [2, null], [3, null], [4, null], [5, null]] }
      expected: [[1, null], [2, null], [3, null], [4, null], [5, null]]
    - input: { head: [[-1, 0], [0, 1], [1, 0]] }
      expected: [[-1, 0], [0, 1], [1, 0]]

description: |
  A linked list of length `n` is given such that each node contains an additional random pointer, which could point to any node in the list, or `null`.

  Construct a **deep copy** of the list. The deep copy should consist of exactly `n` **brand new** nodes, where each new node has its value set to the value of its corresponding original node. Both the `next` and `random` pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. **None of the pointers in the new list should point to nodes in the original list**.

  For example, if there are two nodes `X` and `Y` in the original list, where `X.random --> Y`, then for the corresponding two nodes `x` and `y` in the copied list, `x.random --> y`.

  Return *the head of the copied linked list*.

  The linked list is represented in the input/output as a list of `n` nodes. Each node is represented as a pair of `[val, random_index]` where:

  - `val`: an integer representing `Node.val`
  - `random_index`: the index of the node (range from `0` to `n-1`) that the `random` pointer points to, or `null` if it does not point to any node.

  Your code will **only** be given the `head` of the original linked list.

constraints: |
  - `0 <= n <= 1000`
  - `-10^4 <= Node.val <= 10^4`
  - `Node.random` is `null` or is pointing to some node in the linked list.

examples:
  - input: "head = [[7,null],[13,0],[11,4],[10,2],[1,0]]"
    output: "[[7,null],[13,0],[11,4],[10,2],[1,0]]"
    explanation: "A list with 5 nodes. Node 0 has value 7 and random points to null. Node 1 has value 13 and random points to node 0. Node 2 has value 11 and random points to node 4. And so on. The output is a deep copy with the same structure."
  - input: "head = [[1,1],[2,1]]"
    output: "[[1,1],[2,1]]"
    explanation: "Two nodes where both random pointers point to node 1 (the second node with value 2)."
  - input: "head = [[3,null],[3,0],[3,null]]"
    output: "[[3,null],[3,0],[3,null]]"
    explanation: "Three nodes all with value 3. The middle node's random pointer points to the first node."

explanation:
  intuition: |
    Imagine you're tasked with duplicating a spider web where each thread (the `next` pointer) connects to the next junction, but there are also random silk threads (`random` pointers) that can connect any junction to any other junction — or nowhere at all.

    The challenge is that when you're building your copy, you need to connect the `random` pointer of a copied node to another *copied* node. But how do you find the copied version of the node that the original `random` points to?

    Think of it like this: if you're copying a contact list where each person has a "best friend" field pointing to another person in the same list, you can't fill in the "best friend" until you've created entries for *everyone*. The core insight is that you need a way to **map original nodes to their copies**.

    There are two elegant approaches:
    1. **Hash Map**: Use a dictionary to store the mapping from each original node to its copy. This makes lookups instant but uses O(n) extra space.
    2. **Interweaving**: Cleverly weave copied nodes directly into the original list (A → A' → B → B' → ...), so each copy sits right next to its original. The original's `random.next` gives you the copy's random target. Then unweave to separate the lists.

  approach: |
    We'll describe the **Hash Map Approach** as the primary solution due to its clarity:

    **Step 1: Handle the edge case**

    - If `head` is `null`, return `null` immediately

    &nbsp;

    **Step 2: First pass — create all copied nodes**

    - Traverse the original list
    - For each original node, create a new node with the same value
    - Store the mapping `original_node → copied_node` in a hash map
    - This ensures we have all copies created before wiring up pointers

    &nbsp;

    **Step 3: Second pass — wire up `next` and `random` pointers**

    - Traverse the original list again
    - For each original node:
      - Set `copy.next = hash_map[original.next]` (or `null` if `original.next` is `null`)
      - Set `copy.random = hash_map[original.random]` (or `null` if `original.random` is `null`)
    - The hash map allows O(1) lookup of any copied node

    &nbsp;

    **Step 4: Return the copied head**

    - Return `hash_map[head]` — the copy of the original head node

  common_pitfalls:
    - title: Copying Random Pointers to Original Nodes
      description: |
        A common mistake is to set `copy.random = original.random`. This makes the copy's random pointer point to a node in the *original* list, not the copied list.

        The problem explicitly states: "None of the pointers in the new list should point to nodes in the original list."

        You must translate every pointer to point to the corresponding *copy*.
      wrong_approach: "copy.random = original.random"
      correct_approach: "copy.random = hash_map[original.random]"

    - title: Single Pass Without Pre-Creating Nodes
      description: |
        You might try to copy nodes and wire pointers in a single pass. But consider: when processing node A, its `random` might point to node Z which you haven't created yet.

        Either use two passes (create all nodes first, then wire pointers), or create nodes on-demand and check the hash map before creating duplicates.
      wrong_approach: "Single pass assuming nodes exist"
      correct_approach: "Two-pass or create-on-demand with hash map"

    - title: Not Handling Null Pointers
      description: |
        Both `next` and `random` can be `null`. When looking up in the hash map, ensure you handle the case where the key is `null`.

        In Python, `hash_map.get(None)` returns `None`, which is correct. In other languages, you may need explicit null checks.
      wrong_approach: "Unconditionally accessing hash_map[node]"
      correct_approach: "Check if node is null before hash map lookup"

  key_takeaways:
    - "**Hash map for node mapping**: When cloning graph-like structures, a hash map from original to copy nodes enables O(1) pointer translation"
    - "**Two-pass pattern**: Create all nodes first, then wire connections — this avoids forward reference problems"
    - "**Space-time tradeoff**: The interweaving approach achieves O(1) space but is trickier to implement; hash map is clearer at O(n) space"
    - "**Deep copy fundamentals**: This problem teaches the core concept of deep copying interconnected structures — applicable to graphs, trees with parent pointers, and more"

  time_complexity: "O(n). We traverse the list twice — once to create copies, once to wire pointers. Each node is visited a constant number of times."
  space_complexity: "O(n). The hash map stores a mapping for each of the `n` nodes. The output list itself also uses O(n) space, but that's required by the problem."

solutions:
  - approach_name: Hash Map
    is_optimal: true
    code: |
      class Node:
          def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
              self.val = x
              self.next = next
              self.random = random

      def copyRandomList(head: 'Node') -> 'Node':
          if not head:
              return None

          # Map from original node to its copy
          old_to_new = {}

          # First pass: create all copied nodes
          current = head
          while current:
              old_to_new[current] = Node(current.val)
              current = current.next

          # Second pass: wire up next and random pointers
          current = head
          while current:
              copy = old_to_new[current]
              # Wire next pointer (use .get() to handle None gracefully)
              copy.next = old_to_new.get(current.next)
              # Wire random pointer
              copy.random = old_to_new.get(current.random)
              current = current.next

          # Return the copy of the head
          return old_to_new[head]
    explanation: |
      **Time Complexity:** O(n) — Two linear passes through the list.

      **Space Complexity:** O(n) — Hash map stores n mappings.

      The hash map approach is intuitive: first create all copies, then use the map to translate every pointer from the original domain to the copy domain. The `.get()` method handles `None` keys gracefully by returning `None`.

  - approach_name: Interweaving Nodes
    is_optimal: false
    code: |
      def copyRandomList(head: 'Node') -> 'Node':
          if not head:
              return None

          # Step 1: Interweave copied nodes into the original list
          # Original: A -> B -> C
          # After:    A -> A' -> B -> B' -> C -> C'
          current = head
          while current:
              copy = Node(current.val)
              copy.next = current.next
              current.next = copy
              current = copy.next

          # Step 2: Wire up random pointers for copied nodes
          current = head
          while current:
              copy = current.next
              if current.random:
                  # The copy of current.random is current.random.next
                  copy.random = current.random.next
              current = copy.next

          # Step 3: Unweave the lists to separate original and copy
          current = head
          copy_head = head.next
          while current:
              copy = current.next
              # Restore original list
              current.next = copy.next
              # Wire copy list
              if copy.next:
                  copy.next = copy.next.next
              current = current.next

          return copy_head
    explanation: |
      **Time Complexity:** O(n) — Three linear passes through the list.

      **Space Complexity:** O(1) — No hash map; copies are woven into the original list temporarily.

      This clever approach avoids extra space by placing each copy immediately after its original. The trick is that `original.random.next` gives us the copy of the random target. After wiring random pointers, we unweave to restore the original list and extract the copy. While space-efficient, it's more complex and modifies the original list temporarily.
