[Leetcode解題] 381. Insert Delete GetRandom O(1) - Duplicates allowed
27 September 2024
medium
hashmap
array
381. Insert Delete GetRandom O(1) - Duplicates allowed
題目描述
381. Insert Delete GetRandom O(1) - Duplicates allowed
RandomizedCollection 是一個支援重複元素(multiset)的數據結構,需要支援以下三種操作:
bool insert(int val):將元素val插入到集合中,即使該元素已存在。返回值為true當該元素之前不在集合中,否則返回false。bool remove(int val):從集合中移除一個val,如果val存在,則返回true,否則返回false。如果有多個相同元素,只移除其中一個。int getRandom():隨機返回集合中的一個元素。每個元素被返回的機率應與它在集合中的出現次數成正比。
要求所有操作在平均 $O(1)$ 的時間複雜度內完成。
解題思路
這題與380. Insert Delete GetRandom O(1) 非常類似,只差在能否重複,本題也是藉由與陣列中最後一個交換的思路去做刪除元素的。
為了達到平均 $O(1)$ 的時間複雜度,我們可以使用以下結構來儲存數據:
vector<int> nums:用來儲存所有的元素。由於vector支援在尾部插入和刪除操作,這能幫助我們在 $O(1)$ 的時間內進行隨機取出、插入與移除操作。unordered_map<int, unordered_set<int>> numToIdx:用來映射每個數字到其在nums中所有出現的位置。這允許我們在 $O(1)$ 的時間內找到特定值並進行刪除操作。
插入操作 (insert)
- 每次插入一個數字時,首先將其插入到
nums的尾部,並將其在nums中的索引加入到numToIdx的對應集合中。 - 如果該數字之前不在集合中,返回
true;否則返回false。
刪除操作 (remove)
- 若
numToIdx中找不到val,直接返回false。 - 若找到,隨便取一個
val在nums中的索引,然後將nums尾部的數字與這個索引對應的數字進行交換,並更新numToIdx中的索引信息,最後將nums尾部的數字刪除。 - 若該數字的所有出現位置都已被刪除,將其從
numToIdx中刪除。
隨機獲取 (getRandom)
- 直接從
nums中隨機返回一個元素。由於nums儲存了所有的元素,並且每個元素的出現次數和集合中的數據一致,所以可以通過rand()函數隨機選取一個索引,達到返回隨機元素的效果。
Python 實作
class RandomizedCollection:
def __init__(self):
self.nums = [] # 儲存所有的數字
self.num_to_idx = {} # 映射數字到其索引的集合
def insert(self, val: int) -> bool:
# 插入數字到nums,並更新索引
not_present = val not in self.num_to_idx
if not_present:
self.num_to_idx[val] = set()
self.nums.append(val)
self.num_to_idx[val].add(len(self.nums) - 1)
return not_present
def remove(self, val: int) -> bool:
if val not in self.num_to_idx or len(self.num_to_idx[val]) == 0:
return False
# 取出val的其中一個索引
remove_idx = next(iter(self.num_to_idx[val]))
self.num_to_idx[val].remove(remove_idx)
last_val = self.nums[-1]
# 若不是最後一個元素,交換移除位置和最後一個元素
if remove_idx != len(self.nums) - 1:
self.nums[remove_idx] = last_val
self.num_to_idx[last_val].remove(len(self.nums) - 1)
self.num_to_idx[last_val].add(remove_idx)
self.nums.pop()
if len(self.num_to_idx[val]) == 0:
del self.num_to_idx[val]
return True
def getRandom(self) -> int:
return random.choice(self.nums)
C++ 實作
class RandomizedCollection {
public:
RandomizedCollection() {
}
bool insert(int val) {
bool nopresent = numToIdx.find(val)==numToIdx.end();
nums.push_back(val);
numToIdx[val].insert(nums.size()-1);
return nopresent;
}
bool remove(int val) {
if (numToIdx.find(val)==numToIdx.end()) return false;
int delIdx = *(numToIdx[val].begin());
numToIdx[val].erase(delIdx);
if (delIdx != nums.size()-1){
numToIdx[nums.back()].erase(nums.size()-1);
numToIdx[nums.back()].insert(delIdx);
swap(nums[delIdx], nums.back());
}
if (numToIdx[val].size()==0) numToIdx.erase(val);
nums.pop_back();
return true;
}
int getRandom() {
return nums[rand()%nums.size()];
}
private:
vector<int> nums;
unordered_map<int, unordered_set<int>> numToIdx;
};
/**
* Your RandomizedCollection object will be instantiated and called as such:
* RandomizedCollection* obj = new RandomizedCollection();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
時間複雜度分析
- 插入操作
insert:- 插入元素到
nums尾部的時間為 $O(1)$。 - 更新
num_to_idx時間為 $O(1)$。 - 總時間複雜度:$O(1)$。
- 插入元素到
- 刪除操作
remove:- 找到
val的索引並移除:$O(1)$。 - 如果刪除的不是最後一個元素,進行交換操作:$O(1)$。
- 總時間複雜度:$O(1)$。
- 找到
- 隨機返回
getRandom:- 隨機返回
nums中的元素:$O(1)$。 - 總時間複雜度:$O(1)$。
- 隨機返回
總結
這個題目的關鍵是利用 vector 和 unordered_map<int, unordered_set<int>> 結合,確保所有操作都能在平均 $O(1)$ 的時間內完成。vector 保證了隨機訪問與插入刪除的效率,unordered_map<int, unordered_set<int>> 使我們能夠快速地查找元素的位置並進行更新。