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. You need to design a system that tracks passengers checking in and out of stations, and efficiently calculates average travel times between stations. Which approach best balances time and space efficiency for frequent queries?
easy
A. Use hash maps to store running totals of travel times and counts per route.
B. Store all trips explicitly and scan them to compute averages on demand.
C. Use dynamic programming to precompute all possible route averages.
D. Use a priority queue to keep track of the shortest travel times per route.

Solution

  1. Step 1: Understand the problem requirements

    The system must support frequent check-ins, check-outs, and quick average time queries between stations.
  2. Step 2: Evaluate approaches

    Storing all trips (B) leads to slow queries. Dynamic programming (C) is not applicable as routes are dynamic and not fixed. Priority queues (D) do not help compute averages efficiently. Hash maps with running totals (A) allow O(1) updates and queries.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Hash maps maintain running sums and counts for O(1) average time queries [OK]
Hint: Running totals enable O(1) average queries [OK]
Common Mistakes:
  • Thinking storing all trips is efficient for queries
2. Consider the following Python code snippet implementing the optimized Insert Delete GetRandom O(1) with duplicates data structure. After executing the sequence of operations below, what is the return value of the final getRandom() call? Operations: insert(1), insert(1), insert(2), remove(1), getRandom()
import random

class Node:
    def __init__(self, val):
        self.val = val
        self.prev = null
        self.next = null

class DoublyLinkedList:
    def __init__(self):
        self.head = Node(null)
        self.tail = Node(null)
        self.head.next = self.tail
        self.tail.prev = self.head

    def append(self, val):
        node = Node(val)
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev.next = node
        self.tail.prev = node
        return node

    def remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def is_empty(self):
        return self.head.next == self.tail

class RandomizedCollection:
    def __init__(self):
        self.nums = []
        self.idx_map = {}

    def insert(self, val: int) -> bool:
        self.nums.append(val)
        if val not in self.idx_map:
            self.idx_map[val] = set()
        self.idx_map[val].add(len(self.nums) - 1)
        return len(self.idx_map[val]) == 1

    def remove(self, val: int) -> bool:
        if val not in self.idx_map or not self.idx_map[val]:
            return false
        remove_idx = self.idx_map[val].pop()
        last_val = self.nums[-1]
        self.nums[remove_idx] = last_val
        self.idx_map[last_val].add(remove_idx)
        self.idx_map[last_val].discard(len(self.nums) - 1)
        self.nums.pop()
        if not self.idx_map[val]:
            del self.idx_map[val]
        return true

    def getRandom(self) -> int:
        return random.choice(self.nums)

rc = RandomizedCollection()
rc.insert(1)
rc.insert(1)
rc.insert(2)
rc.remove(1)
result = rc.getRandom()
easy
A. Either 1 or 2 but 1 is twice as likely
B. 2
C. Either 1 or 2 with equal probability
D. 1

Solution

  1. Step 1: Trace insertions

    After insert(1), nums=[1], idx_map={1:{0}}; after insert(1), nums=[1,1], idx_map={1:{0,1}}; after insert(2), nums=[1,1,2], idx_map={1:{0,1},2:{2}}.
  2. Step 2: Trace removal of one 1

    Remove one 1: remove_idx=1 (pop from idx_map[1]). Swap last element 2 into index 1: nums=[1,2], idx_map={1:{0},2:{1}}. So now nums has one 1 and one 2.
  3. Step 3: Analyze getRandom probabilities

    nums=[1,2], so getRandom picks index 0 or 1 equally. But initially there were two 1's, after removal only one remains. So 1 and 2 are equally likely, but since the original had duplicates, the final collection has one 1 and one 2.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    After removal, one 1 and one 2 remain, so 1 is as likely as 2 [OK]
Hint: Track indices and swaps carefully to find final array state [OK]
Common Mistakes:
  • Forgetting to update idx_map after swap
  • Assuming getRandom returns only 2
  • Ignoring that one 1 remains after removal
3. You need to design a data structure that supports adding intervals, removing intervals, and querying whether a given range is fully covered by the added intervals. Which approach guarantees efficient updates and queries with logarithmic time complexity per operation?
easy
A. Use a balanced binary search tree (e.g., TreeMap) to store and merge intervals, enabling logarithmic time operations.
B. Use a brute force list of intervals and scan all intervals for each operation.
C. Use dynamic programming to precompute coverage for all possible ranges.
D. Use a greedy algorithm that always merges intervals only when queried.

Solution

  1. Step 1: Understand the problem requirements

    The data structure must support add, remove, and query operations on intervals efficiently.
  2. Step 2: Evaluate approaches

    Brute force scanning is O(n) per operation, which is inefficient. DP is not suitable for dynamic interval updates. Greedy merging only on query delays merging and leads to inefficient queries. Balanced BST with interval merging maintains sorted intervals and merges overlapping ones, supporting O(log n) operations.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Balanced BST with merging -> O(log n) per operation [OK]
Hint: Balanced BST with merging enables efficient interval operations [OK]
Common Mistakes:
  • Thinking DP or greedy solves dynamic interval updates efficiently
4. What is the time complexity of adding a number and then finding the median using the balanced two heaps approach with lazy deletion for a data stream of size n?
medium
A. O(log n) per addNum and O(log n) per findMedian
B. O(n) per addNum and O(1) per findMedian
C. O(log n) per addNum and O(1) per findMedian
D. O(1) per addNum and O(n) per findMedian

Solution

  1. Step 1: Analyze addNum complexity

    Adding a number involves pushing to a heap and balancing heaps, each O(log n) operations.
  2. Step 2: Analyze findMedian complexity

    Median is retrieved from the top of heaps without modification, O(1) time.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Insertion is O(log n), median retrieval O(1) [OK]
Hint: Heap push/pop are O(log n), median peek is O(1) [OK]
Common Mistakes:
  • Assuming sorting each query is O(log n)
  • Confusing addNum with findMedian complexity
5. Suppose the data stream allows duplicate numbers to be removed after insertion, requiring support for removeNum(num). Which modification to the balanced two heaps approach is necessary to maintain correct median retrieval?
hard
A. Use a balanced binary search tree instead of heaps to allow efficient removals.
B. Ignore removals and only add numbers, as removals break the heap structure.
C. Rebuild both heaps from scratch after every removal to maintain heap properties.
D. Add a delayed deletion map to mark elements for lazy removal and prune heaps during addNum and findMedian.

Solution

  1. Step 1: Understand removal challenges

    Heaps do not support efficient arbitrary removals, so direct removal breaks heap properties.
  2. Step 2: Use lazy deletion

    Maintain a delayed deletion map to mark elements for removal and prune them lazily during heap operations, preserving heap structure and efficiency.
  3. Step 3: Avoid costly rebuilds

    Rebuilding heaps on every removal is inefficient; ignoring removals is incorrect.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Lazy deletion with pruning maintains correctness and efficiency [OK]
Hint: Lazy deletion map enables efficient removals in heaps [OK]
Common Mistakes:
  • Rebuilding heaps on every removal
  • Ignoring removals breaks correctness