Bird
Raised Fist0
Interview Prepcustom-data-structuresmediumAmazonMicrosoftGoogleFacebook

Copy List with Random Pointer

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Start copying from head node (val=7)

The algorithm begins by calling dfs on the head node with value 7 to start the deep copy process.

💡 Starting at the head is essential because the entire list is reachable from here.
Line:return dfs(head)
💡 The recursion will traverse all nodes reachable from head.
📊
Copy List with Random Pointer - Watch the Algorithm Execute, Step by Step
Watching each recursive call and memoization step reveals how the algorithm avoids infinite loops and ensures a deep copy with correct random pointers.
Step 1/20
·Active fillAnswer cell
advance
7
13
11
10
1
compare
7
13
11
10
1
compare
7
13
11
10
1
insert
7
13
11
10
1
7
advance
7
13
11
10
1
7
insert
7
13
11
10
1
7
13
advance
7
13
11
10
1
7
13
insert
7
13
11
10
1
7
13
11
advance
7
13
11
10
1
7
13
11
insert
7
13
11
10
1
7
13
11
10
advance
7
13
11
10
1
7
13
11
10
insert
7
13
11
10
1
7
13
11
10
1
compare
7
13
11
10
1
7
13
11
10
1
advance
7
13
11
10
1
7
13
11
10
1
prune
7
13
11
10
1
7
13
11
10
1
connect
7
13
11
10
1
advance
10
11
7
13
11
10
1
prune
11
10
connect
7
13
11
10
1
reconstruct
7
13
11
10
1
Result: [[7, null], [13, 0], [11, 4], [10, 2], [1, 0]]

Key Takeaways

Memoization is critical to avoid infinite recursion and duplicate copies when copying random pointers.

Without memoization, the algorithm would endlessly recurse on cycles formed by random pointers.

The algorithm copies next pointers first, then random pointers, ensuring the copied list structure is built before linking random pointers.

This order clarifies how the recursion builds the list step-by-step.

Each recursive call corresponds to copying one node, and the visualization shows how the recursion stack grows and shrinks.

Seeing each call separately helps understand recursion depth and flow.

Practice

(1/5)
1. Given the following code for partitioning a linked list around value x = 3, and the input list: 1 -> 4 -> 2 -> 5, what is the final reordered list returned by the function?
easy
A. 1 -> 4 -> 2 -> 5
B. 1 -> 2 -> 4 -> 5
C. 2 -> 1 -> 4 -> 5
D. 4 -> 1 -> 2 -> 5

Solution

  1. Step 1: Trace initial pointers with input 1 -> 4 -> 2 -> 5 and x=3

    Start with dummy -> 1 -> 4 -> 2 -> 5, less_tail and prev at dummy, current at 1.
  2. Step 2: Process nodes

    Node 1 < 3, less_tail.next == current, move less_tail and prev forward. Node 4 >= 3, move prev and current forward. Node 2 < 3, less_tail.next != current, so remove 2 and insert after less_tail. Final list: 1 -> 2 -> 4 -> 5.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Nodes less than 3 appear first in original order, then others [OK]
Hint: Track less_tail and prev carefully to avoid skipping nodes [OK]
Common Mistakes:
  • Swapping values instead of nodes
  • Incorrect pointer updates causing lost nodes
  • Assuming order changes within partitions
2. Given the following code snippet for splitting a linked list into k=3 parts, and the input list 1->2->3->4, what is the returned list of parts (values only)?
easy
A. [[1, 2], [3, 4], []]
B. [[1, 2], [3], [4]]
C. [[1, 2], [3], [4, None]]
D. [[1], [2], [3, 4]]

Solution

  1. Step 1: Calculate total nodes and part sizes

    Total nodes = 4, k = 3, so part_size = 1, remainder = 1. First remainder parts get one extra node.
  2. Step 2: Assign nodes to parts

    Part 0 size = 2 nodes (1,2), Part 1 size = 1 node (3), Part 2 size = 1 node (4). The returned parts are [[1,2],[3],[4]].
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Sum of parts equals original list length and sizes differ by at most one [OK]
Hint: Remainder nodes assigned to first parts -> first part larger [OK]
Common Mistakes:
  • Assigning remainder nodes to last parts
  • Off-by-one in part sizes
  • Returning fewer than k parts
3. Consider the following buggy recursive code to convert a binary number in a linked list to an integer. Which line contains the subtle bug that causes incorrect output for single-node lists?
medium
A. Line X: using addition instead of bitwise OR to accumulate bits
B. Line 7: base case check for None node
C. Line 3: __init__ method of ListNode
D. Line 9: recursive call with node.next and updated acc

Solution

  1. Step 1: Identify the accumulation operation

    The code uses bitwise OR instead of addition to combine bits: acc = (acc << 1) | node.val.
  2. Step 2: Understand impact on single-node lists

    Bitwise OR correctly sets bits without carry, ensuring accurate accumulation for all list lengths.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Bitwise OR correctly sets bits without carry, addition may overflow [OK]
Hint: Use bitwise OR, not addition, to accumulate bits [OK]
Common Mistakes:
  • Using + instead of | causes wrong bit accumulation
  • Misunderstanding bitwise operations vs arithmetic
  • Assuming addition and OR are interchangeable for bits
4. What is the time complexity of the in-place iterative flattening algorithm for a multilevel doubly linked list with total n nodes, considering the worst-case scenario where each node has a child list?
medium
A. O(n) amortized, since the total number of pointer updates and traversals sums to linear over all nodes.
B. O(n) because each node is visited a constant number of times during the flattening process.
C. O(n^2) because for each node with a child, we traverse its entire child list to find the tail.
D. O(n log n) due to repeated tail searches in nested child lists.

Solution

  1. Step 1: Analyze tail-finding loop

    Although tail is found by traversing child lists, each node is visited at most once as tail or curr.
  2. Step 2: Amortized analysis

    All next pointers are traversed linearly; tail searches do not overlap nodes multiple times, so total work is linear.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Each node processed once, tail searches amortize to O(n) [OK]
Hint: Tail searches look nested but total visits are linear [OK]
Common Mistakes:
  • Assuming tail search is repeated fully for each child causing O(n^2)
  • Confusing amortized with worst-case per iteration
  • Ignoring that nodes are not revisited multiple times
5. Suppose the problem is modified so that nodes can be reused multiple times (i.e., after reversing a group, nodes can appear again in subsequent groups). Which of the following changes to the algorithm correctly handles this scenario?
hard
A. Modify the algorithm to create new nodes for each group reversal to avoid modifying original nodes in place.
B. This problem cannot be solved by reversal; instead, use a queue to simulate repeated node usage.
C. Use recursion with memoization to store reversed groups and reuse them without modifying the original list.
D. Use the same iterative reversal approach but reset pointers to allow reusing nodes in multiple groups.

Solution

  1. Step 1: Understand node reuse implication

    Reusing nodes means original nodes must remain unchanged or duplicated to appear multiple times.
  2. Step 2: Evaluate algorithm modifications

    In-place reversal modifies nodes destructively, so creating new nodes for each group is necessary to preserve original nodes.
  3. Step 3: Assess other options

    Resetting pointers (A) breaks list integrity; recursion with memoization (C) is complex and not standard; queue simulation (B) does not solve reversal reuse.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Duplicating nodes preserves original list for reuse [OK]
Hint: Node reuse requires duplication, not in-place reversal [OK]
Common Mistakes:
  • Trying to reuse nodes in place
  • Ignoring list integrity
  • Assuming recursion memoization solves reuse