Start learning free

LESSON 309 OF 414 · LINKED LISTS

Copy List with Random Pointer in Scala: O(1) Space by Interleaving

Practice O(n) Read the full explanation

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, the value of the node
  • 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.

 

Example 1:

Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]

Example 2:

Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]

Example 3:

Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]

Example 4:

Input: head = []
Output: []
Explanation: The given linked list is empty (null), so return null.

 

Constraints:

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

Note:

Your algorithm should run in O(n) time and use O(1) extra space.


STEP 1 OF 22
    Ctrl + Enter to check

    SCALA · LINKED LISTS

    Copy List Random Pointer in Scala, explained

    Problem overview

    The method copyRandomList receives the head of a linked list of ListNode objects. Each node has a value, a next pointer and a random pointer that may point to any node in the list or to null. The method must return a deep copy: n new nodes with the same values, whose next and random pointers lead only to other new nodes. For an empty list it returns null.

    The examples write a node as [val, random_index]. In [[1,1],[2,1]], both nodes have their random pointer on index 1, the node holding 2. The copy has the same shape, with both random pointers on the copy's second node. This lesson's note asks for O(n) time and only O(1) extra space, so storing a map from originals to copies is not allowed.

    Approach: put each copy right after its original

    The map in the usual solution answers one question: "which copy belongs to this original?" The same answer can be stored in the list itself. If every copy sits directly after its original, the copy of any node X is simply X.next, with no lookup table.

    That leads to three passes:

    1. Interleave. Insert a copy after each original, turning A → B → C into A → A' → B → B' → C → C'.
    2. Set random pointers. For an original with random pointing at X, its copy's random must be the copy of X, which is X.next. So copy.random = node.random.next.
    3. Separate. Unweave the two lists, restoring the original and linking the copies together. The caller still owns the original list and expects it unchanged.

    The only new memory is the copies themselves, which the result needs anyway, so the extra space is O(1).

    Step by step

    1. Guard the empty list. If head is null, return null. Later code reads head.next.
    2. Create the walker, var node = head.
    3. First pass: loop while node != null.
    4. Save the original successor in val nextNode = node.next, because inserting the copy overwrites node.next.
    5. Create the copy, val copyNode = ListNode(node.`val`). Only the value is copied now. The backticks are needed because val is a Scala keyword.
    6. Insert it by setting node.next = copyNode.
    7. Reconnect the chain with copyNode.next = nextNode.
    8. Skip the copy by moving to nextNode. Moving to node.next would land on the copy and create copies of copies without end.
    9. Reset the walker to head for the second pass.
    10. Second pass: loop while node != null. Each iteration visits only originals.
    11. Take the copy with val copyNode = node.next.
    12. Set its random pointer. If node.random is not null, set copyNode.random = node.random.next, the copy of the target.
    13. Jump to the next original: node = copyNode.next.
    14. Remember the new head before the lists are separated: val newHead = head.next.
    15. Reset the walker to head again.
    16. Third pass: loop while node != null.
    17. Read the pair: val copyNode = node.next, and the next original val nextNode = copyNode.next, before any link changes.
    18. Link the copies: if nextNode is not null, set copyNode.next = nextNode.next. For the last copy, next is already null.
    19. Restore the original with node.next = nextNode.
    20. Advance with node = nextNode.
    21. Return newHead as the last expression.

    The solution in Scala

    class Solution {
        def copyRandomList(head: ListNode): ListNode = {
            if (head == null) {
                return null
            }
    
            var node = head
            while (node != null) {
                val nextNode = node.next
                val copyNode = ListNode(node.`val`)
                node.next = copyNode
                copyNode.next = nextNode
                node = nextNode
            }
    
            node = head
            while (node != null) {
                val copyNode = node.next
                if (node.random != null) {
                    copyNode.random = node.random.next
                }
                node = copyNode.next
            }
    
            val newHead = head.next
            node = head
            while (node != null) {
                val copyNode = node.next
                val nextNode = copyNode.next
                if (nextNode != null) {
                    copyNode.next = nextNode.next
                }
                node.next = nextNode
                node = nextNode
            }
    
            newHead
        }
    }
    

    The Scala details worth knowing:

    • return only for the early exit. The guard uses return null to leave the method early, while the normal result is the last expression, newHead. Scala code usually avoids return, but an early exit is the common case where it reads well.
    • Scoped vals. Each loop body declares its own copyNode and nextNode. They are separate for each pass and each iteration, and they cannot be reassigned.
    • var node is reused. The same variable walks all three passes, and each pass starts by assigning node = head.
    • Fields are assignable. node.next = copyNode changes a field of an existing object, which is allowed even though node is the loop's walker. The list's structure, not the variable, is what changes.
    • Null checks. null is a valid ListNode value, so every dereference that might see it, node.random.next and nextNode.next, is guarded.

    Complexity

    Time: O(n). Each of the three passes visits every original node once and does constant work per node.

    Space: O(1) extra. Apart from the n new nodes, which are the output, the method uses a few references. No map or array is built.

    Edge cases and common mistakes

    • Empty list. The guard returns null before head.next is read.
    • Single node. The first pass makes A → A', the second pass copies its random pointer if it has one, and the third pass leaves A with next of null and A' as the new head.
    • Setting random pointers before interleaving is complete. The target's copy must already be in place, which is why this is a separate second pass.
    • Splitting during the random pass. Unweaving too early destroys the X.next trick for nodes that have not been handled yet.
    • Not restoring the original. If the third pass skipped node.next = nextNode, the caller's list would still contain the copies.
    • Losing the new head. After the third pass head.next is the original second node again, so newHead must be saved before it.
    • A null random pointer. Reading node.random.next without the check would throw a NullPointerException.

    Testing

    • [[7,null],[13,0],[11,4],[10,2],[1,0]] gives the same list, built from new nodes, and the original is left as it was.
    • [[1,1],[2,1]] gives [[1,1],[2,1]].
    • [[3,null],[3,0],[3,null]] gives [[3,null],[3,0],[3,null]].
    • [] gives null.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free