题目要求
实现 RandomizedSet 类:
- RandomizedSet() 初始化 RandomizedSet 对象。
- bool insert(int val) 将不存在的项目 val 插入随机集合。如果项目不存在,则返回 true,否则返回 false。
- bool remove(int val) 从集合中删除项目 val(如果存在)。如果存在,则返回 true,否则返回 false。
- int getRandom() 从当前元素集中随机返回一个元素(保证调用此方法时至少有一个元素存在)。每个元素被返回的概率必须相同。
您必须实现该类的函数,使每个函数的平均工作时间复杂度为 O(1)。
思路
对于一个标准的 HashSet
,你能否在 O(1)
的时间内实现 getRandom
函数?
其实是不能的,因为根据刚才说到的底层实现,元素是被哈希函数「分散」到整个数组里面的,更别说还有拉链法等等解决哈希冲突的机制,基本做不到 O(1)
时间等概率随机获取元素。
换句话说,对于 getRandom
方法,如果想「等概率」且「在 O(1)
的时间」取出元素,一定要满足:
底层用数组实现,且数组必须是紧凑的,这样我们就可以直接生成随机数作为索引,选取随机元素。
但如果用数组存储元素的话,常规的插入,删除的时间复杂度又不可能是 O(1)
。
然而,对数组尾部进行插入和删除操作不会涉及数据搬移,时间复杂度是 O(1)
。
所以,如果我们想在 O(1)
的时间删除数组中的某一个元素 val
,可以先把这个元素交换到数组的尾部,然后再 pop
掉。
代码
class RandomizedSet {
public:
vector<int> nums;
// 记录每个元素对应在 nums 中的索引
unordered_map<int, int> valToIndex;
RandomizedSet() {
}
bool insert(int val) {
if (valToIndex.count(val)) {
return false;
}
// 若 val 不存在,插入到 nums 尾部,
// 并记录 val 对应的索引值
valToIndex[val] = nums.size();
nums.push_back(val);
return true;
}
bool remove(int val) {
if (!valToIndex.count(val)) {
return false;
}
int index = valToIndex[val];
// 将最后一个元素对应的索引修改为 index
valToIndex[nums.back()] = index;
// 交换 val 和最后一个元素
swap(nums[index], nums.back());
// 在数组中删除元素 val
nums.pop_back();
// 删除元素 val 对应的索引
valToIndex.erase(val);
return true;
}
int getRandom() {
// 随机获取 nums 中的一个元素
return nums[rand() % nums.size()];
}
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/