Start learning free

LESSON 323 OF 445 · LINKED LISTS

Copy List with Random Pointer in Rust: O(n) with a HashMap

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 None.

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 None 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 (None), so return None.

 

Constraints:

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

Note:

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


STEP 1 OF 13
    Ctrl + Enter to check

    RUST · LINKED LISTS

    Copy List Random Pointer in Rust, explained

    Problem overview

    Each node of a linked list has a val, a next link and a random link that can point to any node of the list or to nothing. In this course a link is Option<Rc<RefCell<ListNode>>>, written Link. The function copy_random_list receives the head and must return the head of a deep copy: exactly n new nodes with the same values, where every next and random link of the copy points to nodes of the copy, never to the original.

    Take the list [[7,null],[13,0],[11,4],[10,2],[1,0]], where each pair is the value and the index of the random target. The node 13 has its random link pointing back to the node 7, the first node. In the copy, the new 13 must point to the new 7, not to the old one. An empty list returns None.

    Approach: map every original node to its copy

    Copying the values and the next links is easy: walk the list and build new nodes. The hard part is random. When you create the copy of a node, its random target may be a node further down the list that has no copy yet, or one you have already passed.

    The way out is two passes. The first pass creates one copy for every original node and records the pairing in a hash map from original to copy. The second pass wires the links: for each original node, look up its copy, then look up the copies of its next and random targets in the same map. Since every copy exists by then, every lookup succeeds.

    The map uses the node's address as its key, not its value. The lesson's third example, [[3,null],[3,0],[3,null]], has three nodes with the same value. A map keyed by value would mix them up, but their addresses are always different. Rc::as_ptr gives that address.

    The cost is O(n) time and O(n) extra space for the map. A different lesson on this problem trades the map away for an interleaving trick that needs O(1) extra space.

    Step by step

    1. Create the map. copies is a mutable HashMap::new(). You don't write its types: Rust infers them from the first insert.
    2. Create the cursor. node starts as head.clone(). Cloning an Rc copies the pointer, not the node, and leaves head available for the second pass.
    3. Loop over the originals with while let Some(curr) = node.
    4. Create the copy. Insert into copies, under the key Rc::as_ptr(&curr), a new node made by ListNode::new(curr.borrow().val). Only the value is copied for now.
    5. Advance. The pattern moved the value out of node, so assign node = curr.borrow().next.clone() before the loop tests it again.
    6. Write a lookup closure. copy_of takes a &Link of the original list and returns the matching link of the copy. link.as_ref().map(...) keeps None as None, and for a node x it finds the copy by address and clones the Rc.
    7. Restart the cursor with node = head.clone().
    8. Loop again over the originals, naming the node curr.
    9. Find the copy of curr with &copies[&Rc::as_ptr(&curr)].
    10. Wire next. Set the copy's next to copy_of(&curr.borrow().next).
    11. Wire random the same way from curr.borrow().random.
    12. Advance along the original list.
    13. Return copy_of(&head): the copy of the head, or None for an empty list.

    The solution in Rust

    // The list node, already defined in the program:
    // struct ListNode {
    //     val: i32,
    //     next: Link,
    //     random: Link,
    // }
    // type Link = Option<Rc<RefCell<ListNode>>>;
    // ListNode::new(val) makes a new node, an Rc<RefCell<ListNode>>, whose next and random are None.
    impl Solution {
        fn copy_random_list(head: Link) -> Link {
            let mut copies = HashMap::new();
            let mut node = head.clone();
            while let Some(curr) = node {
                copies.insert(Rc::as_ptr(&curr), ListNode::new(curr.borrow().val));
                node = curr.borrow().next.clone();
            }
            let copy_of = |link: &Link| link.as_ref().map(|x| copies[&Rc::as_ptr(x)].clone());
            node = head.clone();
            while let Some(curr) = node {
                let copy = &copies[&Rc::as_ptr(&curr)];
                copy.borrow_mut().next = copy_of(&curr.borrow().next);
                copy.borrow_mut().random = copy_of(&curr.borrow().random);
                node = curr.borrow().next.clone();
            }
            copy_of(&head)
        }
    }
    

    Rust details worth noticing:

    • Rc and RefCell. Several links can point to the same node, so nodes are shared with Rc, a reference-counted pointer. RefCell allows changing a node through a shared pointer: borrow() reads it and borrow_mut() writes it, with the rules checked while the program runs.
    • Keys by address. Rc::as_ptr(&curr) returns a raw pointer, which can be hashed and compared. Two Rc handles to the same node give the same pointer. Clones made with clone() don't change it.
    • Borrow timing. The line copy.borrow_mut().next = copy_of(&curr.borrow().next); borrows two different nodes, the copy for writing and the original for reading. That is fine. Borrowing the same node mutably and immutably at once would panic when the program runs, not stop the compiler.
    • The closure and the map. copy_of borrows copies without changing it, which is why all inserts come before it. Indexing with copies[&key] panics if the key is missing, and here every node of the list has been inserted.
    • The list is left as it was. The function reads the original nodes and writes only to the copies.
    • Cycles. A random link that points back at an earlier node, or at the node itself, creates a cycle of Rc pointers. Rc counts can't free cycles, so such nodes stay in memory when the program doesn't break the links.

    Complexity

    Time: O(n). The first pass visits each node once with a constant-time insert. The second pass visits each node once with constant-time lookups. Hashing a pointer takes constant time.

    Space: O(n) extra. The map holds one entry per node. The copied list itself is also n nodes, but that is the output.

    Edge cases and common mistakes

    • An empty list. Both loops are skipped, and copy_of(&head) returns None.
    • Random pointing to itself. For [[5,0]], the copy's random points to the copy, because the lookup returns the node's own copy.
    • Random pointing forward or backward. The second pass makes the direction irrelevant, since every copy already exists.
    • Equal values. Keying the map by value would give all three 3s the same copy. Keying by address keeps them apart.
    • Sharing nodes with the original. If you set copy.random = curr.random.clone(), the copy would point into the original list, which breaks the deep copy.
    • Creating copies and wiring links in one pass. A random target further down the list has no copy yet at that moment.
    • Forgetting to advance node. Because while let moves the value out, the compiler stops you from using node again without assigning it first.

    Testing

    • [[7,null],[13,0],[11,4],[10,2],[1,0]] gives the same list, in new nodes.
    • [[1,1],[2,1]] gives [[1,1],[2,1]].
    • [[3,null],[3,0],[3,null]] gives [[3,null],[3,0],[3,null]].
    • [[5,0]] gives [[5,0]].
    • [] gives [].
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free