121 lines
3.3 KiB
Markdown
121 lines
3.3 KiB
Markdown
|
|
---
|
||
|
|
layout: page
|
||
|
|
title: Insert Delete GetRandom O(1)
|
||
|
|
last_solved: 2020-06-12
|
||
|
|
category: hashtable, design
|
||
|
|
leetcode_url: https://leetcode.com/problems/insert-delete-getrandom-o1
|
||
|
|
status: Solved
|
||
|
|
---
|
||
|
|
|
||
|
|
Problem
|
||
|
|
-------
|
||
|
|
|
||
|
|
```
|
||
|
|
Design a data structure that supports all following operations in average O(1) time.
|
||
|
|
|
||
|
|
insert(val): Inserts an item val to the set if not already present.
|
||
|
|
remove(val): Removes an item val from the set if present.
|
||
|
|
getRandom: Returns a random element from current set of elements. Each element must have the same probability of being returned.
|
||
|
|
Example:
|
||
|
|
|
||
|
|
// Init an empty set.
|
||
|
|
RandomizedSet randomSet = new RandomizedSet();
|
||
|
|
|
||
|
|
// Inserts 1 to the set. Returns true as 1 was inserted successfully.
|
||
|
|
randomSet.insert(1);
|
||
|
|
|
||
|
|
// Returns false as 2 does not exist in the set.
|
||
|
|
randomSet.remove(2);
|
||
|
|
|
||
|
|
// Inserts 2 to the set, returns true. Set now contains [1,2].
|
||
|
|
randomSet.insert(2);
|
||
|
|
|
||
|
|
// getRandom should return either 1 or 2 randomly.
|
||
|
|
randomSet.getRandom();
|
||
|
|
|
||
|
|
// Removes 1 from the set, returns true. Set now contains [2].
|
||
|
|
randomSet.remove(1);
|
||
|
|
|
||
|
|
// 2 was already in the set, so return false.
|
||
|
|
randomSet.insert(2);
|
||
|
|
|
||
|
|
// Since 2 is the only number in the set, getRandom always return 2.
|
||
|
|
randomSet.getRandom();
|
||
|
|
|
||
|
|
```
|
||
|
|
|
||
|
|
Solution
|
||
|
|
----------
|
||
|
|
|
||
|
|
The insight is to use 2 hash tables to achive O(1) time.
|
||
|
|
|
||
|
|
The hard part was to achive a O(1) time with getRandom(), we need to make sure it can pick from a hashtable index directly.
|
||
|
|
Therefore, when we delete an element from both hash tables, we can't leave a gap. Writing the code for that was a bit tricky so write out your sample data and write out each step seperately. Like so:
|
||
|
|
|
||
|
|
```
|
||
|
|
# map1: {0:10, 1:20} map1[index] = real value
|
||
|
|
# map2: {10:0, 20:1} map2 is the reverse
|
||
|
|
```
|
||
|
|
|
||
|
|
{% highlight python %}
|
||
|
|
|
||
|
|
class RandomizedSet:
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
|
||
|
|
self.map1 = {}
|
||
|
|
self.map2 = {}
|
||
|
|
self.numElements = 0
|
||
|
|
|
||
|
|
def insert(self, num):
|
||
|
|
|
||
|
|
if num not in self.map2:
|
||
|
|
self.map2[num] = self.numElements
|
||
|
|
self.map1[ self.numElements ] = num
|
||
|
|
self.numElements+=1
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
return False # already present
|
||
|
|
|
||
|
|
|
||
|
|
def remove(self, num):
|
||
|
|
|
||
|
|
if num in self.map2:
|
||
|
|
|
||
|
|
# remove last element
|
||
|
|
if self.map2[num]==self.numElements-1:
|
||
|
|
index = self.map2.pop(num)
|
||
|
|
self.map1.pop(index)
|
||
|
|
self.numElements-=1
|
||
|
|
|
||
|
|
# remove but not last element
|
||
|
|
else:
|
||
|
|
|
||
|
|
# if we remove index, then there will be a gap
|
||
|
|
removedIndex = self.map2.pop(num)
|
||
|
|
self.map1.pop(removedIndex)
|
||
|
|
self.numElements-=1
|
||
|
|
|
||
|
|
# take the last element and fill the gap
|
||
|
|
lastElement = self.map1[self.numElements]
|
||
|
|
saveLastElementIndex = self.map2[lastElement]
|
||
|
|
self.map2[lastElement] = removedIndex
|
||
|
|
|
||
|
|
self.map1.pop(saveLastElementIndex)
|
||
|
|
self.map1[ self.map2[lastElement] ] = lastElement
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
return False # not in set
|
||
|
|
|
||
|
|
|
||
|
|
def getRandom(self):
|
||
|
|
|
||
|
|
index = random.randrange(0, len(self.map1) )
|
||
|
|
return self.map1[index]
|
||
|
|
|
||
|
|
{% endhighlight %}
|
||
|
|
|
||
|
|
|
||
|
|
![image1]()
|