Insert Delete GetRandom O(1) - Duplicates allowed

Here is an implementation for the RandomizedCollection class that satisfies the given constraints:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import random

class RandomizedCollection:

    def __init__(self):
        self.vals = [] # To store the values.
        self.idx = {}  # To store the indexes of the values.

    def insert(self, val: int) -> bool:
        self.vals.append(val)
        if val in self.idx:
            self.idx[val].add(len(self.vals) - 1)
            return False
        else:
            self.idx[val] = {len(self.vals) - 1}
            return True

    def remove(self, val: int) -> bool:
        if val not in self.idx:
            return False
        
        remove_idx = self.idx[val].pop() # Removing an index from the set
        last_val = self.vals[-1] # The value in the last element
        
        # If the set becomes empty, delete the key
        if not self.idx[val]:
            del self.idx[val]
        
        # If we are not removing the last element, replace the removed element with the last element
        if remove_idx != len(self.vals) - 1:
            self.vals[remove_idx] = last_val
            self.idx[last_val].add(remove_idx)
            self.idx[last_val].remove(len(self.vals) - 1)
        
        self.vals.pop()
        return True

    def getRandom(self) -> int:
        return random.choice(self.vals)
  • The insert method appends the value to the vals list and manages its index in the idx dictionary. It returns False if the value is already in the multiset and True otherwise.
  • The remove method removes the value from vals and updates the indexes in idx. It returns False if the value is not present and True otherwise.
  • The getRandom method returns a random element from the vals list, where the probability of each element being returned is linearly related to the number of the same values the multiset contains.

Each function works with an average time complexity of (O(1)), satisfying the constraints.

Identifying Problem Isomorphism

“Insert Delete GetRandom O(1) - Duplicates allowed” has an approximate isomorphism: “Design Underground System”. Both problems require designing a data structure that can efficiently perform certain operations, including insertions and deletions, but with different constraints and requirements.

In “Insert Delete GetRandom O(1) - Duplicates allowed”, you need to design a data structure that supports inserting elements, removing elements, and picking an element at random in constant time, even when there are duplicates in the set.

In “Design Underground System”, you are tasked with designing an underground system that can check in and check out passengers, and get the average travel time between two stations. The check-in and check-out operations are equivalent to the insertion and deletion operations in the other problem.

Although there are commonalities in terms of implementing a specific data structure and performing various operations, the constraints and requirements differ significantly. The first problem requires all operations to be performed in constant time, whereas the second problem does not have this requirement.

“Insert Delete GetRandom O(1) - Duplicates allowed” is more complex due to the constant-time requirement for all operations, as well as the handling of duplicates. On the other hand, “Design Underground System” is simpler as it involves standard operations on a data structure without stringent time complexity requirements.

10 Prerequisite LeetCode Problems

“381. Insert Delete GetRandom O(1) - Duplicates allowed” requires implementing a data structure that supports insert, delete and getRandom operations with average time complexity O(1). Here are 10 problems of lesser complexity that can help prepare you for it:

  1. 1. Two Sum: This is a basic problem to understand the concept of hash tables.

  2. 20. Valid Parentheses: This is a basic problem to understand the concept of using a stack.

  3. 155. Min Stack: This problem introduces you to the concept of designing a stack that supports non-trivial operations.

  4. 232. Implement Queue using Stacks: This problem requires you to design a queue using only stacks.

  5. 380. Insert Delete GetRandom O(1): This problem is very similar to problem 381 but doesn’t allow duplicates. It’s a perfect problem to solve before tackling the problem 381.

  6. 716. Max Stack: This problem introduces you to the concept of designing a stack that supports additional operations.

  7. 705. Design HashSet: This problem is about designing your own data structure which is always a good practice for such questions.

  8. 706. Design HashMap: Similar to the HashSet problem, it’s another good practice on designing data structures.

  9. 208. Implement Trie (Prefix Tree): This problem asks you to design a more complex data structure. It’s good practice for handling more complex design requirements.

  10. 146. LRU Cache: This problem is a more complex design problem that combines the usage of hash table and double linked list.

These cover common data structure operations and the design of data structures, which are required to solve problem 381.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class RandomizedCollection:
    def __init__(self):
        self.vals = [] 
        self.indices = {} 

    def insert(self, val: int) -> bool:
        self.vals.append(val) 
        if val in self.indices: 
            self.indices[val].add(len(self.vals)-1) 
            return False
        else:
            self.indices[val] = {len(self.vals)-1} 
            return True

    def remove(self, val: int) -> bool:
        if val not in self.indices: 
            return False

        index = self.indices[val].pop() 
        last_val = self.vals[-1] 
        if index != len(self.vals)-1: 
            self.vals[index] = last_val 
            self.indices[last_val].discard(len(self.vals)-1) 
            self.indices[last_val].add(index) 

        self.vals.pop() 
        if not self.indices[val]: 
            del self.indices[val] 

        return True

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

Problem Classification

The problem involves creating a data structure to perform insert, delete, and getRandom operations in O(1) time complexity. This is a Data Structure Creation Problem.

Language Agnostic Coding Drills

This code is implementing a randomized collection of elements with support for insert, remove, and getRandom operations. The fundamental concepts involved here are:

  1. Lists: A collection that is ordered and changeable. Allows duplicate members.

  2. Sets: A collection that is unordered and unindexed. No duplicate members.

  3. Dictionaries: A collection that is unordered, changeable and indexed. No duplicate members.

  4. Python Built-in Functions: Using built-in functions like append(), pop(), len(), and random.choice().

  5. Python Classes and Object-Oriented Programming: Creating and managing classes and objects.

  6. Collection Manipulation: Inserting and removing elements in/from collections.

To approach this problem, we need to follow these steps:

  1. We initialize a list to hold the values and a dictionary to hold indices of each value.

  2. In the insert function, we add the value to the list and the index to the dictionary (if the value doesn’t exist in the dictionary, we first initialize a set).

  3. In the remove function, we first check if the value is present. If it is, we take an index of this value and the last value in the list. We remove the index from the set in the dictionary (for both the current value and last value) and add the new index (for the last value). After that, we remove the last value from the list. If the set in the dictionary for the current value is empty, we remove this value from the dictionary.

  4. In the getRandom function, we return a random value from the list.

Drills would look like:

  1. Lists: Understand how to create, access, and manipulate lists.

  2. Sets: Understand how to create sets, and use set operations like add(), pop(), and discard().

  3. Dictionaries: Understand how to create, access, and manipulate dictionaries. Practice how to add, remove, and check the presence of keys.

  4. Python Built-in Functions: Understand the use of built-in functions used in this script.

  5. Python Classes and Object-Oriented Programming: Understand how to create classes, define methods, and create instances(objects) of a class.

  6. Collection Manipulation: Understand how to add and remove elements from collections. Practice with real examples.

Targeted Drills in Python

  1. Lists: Creating, accessing, and manipulating lists.

    1
    2
    3
    4
    5
    6
    7
    
    # create a list
    lst = [1, 2, 3, 4, 5]
    # accessing elements
    print(lst[0]) # prints 1
    # manipulating list
    lst.append(6) # appends 6 to the end of the list
    print(lst) # prints [1, 2, 3, 4, 5, 6]
    
  2. Sets: Creating and using set operations.

    1
    2
    3
    4
    5
    6
    7
    8
    
    # create a set
    s = {1, 2, 3, 4}
    # add element to set
    s.add(5)
    print(s) # prints {1, 2, 3, 4, 5}
    # remove element
    s.discard(5)
    print(s) # prints {1, 2, 3, 4}
    
  3. Dictionaries: Creating, accessing, and manipulating dictionaries.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # create a dictionary
    d = {"key1": "value1", "key2": "value2"}
    # accessing values
    print(d["key1"]) # prints "value1"
    # add new key-value pair
    d["key3"] = "value3"
    print(d) # prints {"key1": "value1", "key2": "value2", "key3": "value3"}
    # check if key is in dictionary
    print("key1" in d) # prints True
    
  4. Python Built-in Functions: Using built-in functions like append(), pop(), len(), and random.choice().

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    import random
    # list operations
    lst = [1, 2, 3, 4]
    lst.append(5) # appends 5 to the list
    lst.pop() # removes last element
    # get length of the list
    print(len(lst)) # prints 4
    # get random choice
    print(random.choice(lst)) # prints a random element from the list
    
  5. Python Classes and Object-Oriented Programming: Creating and managing classes and objects.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    class MyClass:
        def __init__(self, value):
            self.value = value
    
        def get_value(self):
            return self.value
    
    # create object
    obj = MyClass(5)
    # access methods
    print(obj.get_value()) # prints 5
    
  6. Collection Manipulation: Inserting and removing elements in/from collections.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    # list operations
    lst = [1, 2, 3, 4]
    lst.append(5) # appends 5 to the list
    lst.remove(1) # removes 1 from the list
    print(lst) # prints [2, 3, 4, 5]
    # dictionary operations
    d = {"key1": "value1", "key2": "value2"}
    d["key3"] = "value3" # adds a new key-value pair
    del d["key1"] # removes key-value pair with key "key1"
    print(d) # prints {"key2": "value2", "key3": "value3"}
    

You can go through these drills, understand the concepts, and practice them. When you are confident in these areas, you can then integrate these concepts to build the final solution.