题目来源
题目描述
class RandomizedCollection {
public:
RandomizedCollection() {
}
bool insert(int val) {
}
bool remove(int val) {
}
int getRandom() {
}
};
题目解析
思路
和 leetcode:380. O(1) 时间插入、删除和获取随机元素 Insert Delete GetRandom O(1)不同的是,380不运行有重复数字,而本题有重复数字。怎么处理呢?思路类似,同样是map+数组,区别在于map中
- 380题:建立每个数字和其坐标的映射
- 本题:建立每个数字和其出现的所有位置的集合之间的映射
那么:
- 对于insert函数:
- 将val压入num的末尾
- 将val的索引压入map[val]的末尾
- 因为题目要求返回true表示第一次插入,返回false表示不是第一次插入,因此我们直接判断m[val] == 1
- 对于remove函数:
- 先看map中有没有val,没有就直接返回
- 如果有,
- 那么需要删除的元素的索引为:deleteIndex = m[val].back()
- 然后取出待交换的元素: swapVal = vec.back(); swapIndex = m[swapVal].back()
- 将待交换的元素放到待删除的元素那里去:
- 删除元素
注意:
- 我们在建立map的映射时要用堆而不是普通的vector数组,
- 因为我们每次remove操作后都会移除nums数组的尾元素,如果我们用vector来保存数组的指标,而且只移出末尾数字的话,有可能出现前面的坐标大小超过了此时 nums 的大小的情况,就会出错
- 所以我们用优先队列对所有的相同数字的坐标进行自动排序,每次把最大位置的坐标移出即可
class RandomizedCollection {
std::vector<int> nums;
std::unordered_map<int, std::priority_queue<int>> m;
public:
RandomizedCollection() {
}
bool insert(int val) {
m[val].push(nums.size());
nums.push_back(val);
return m[val].size() == 1;
}
bool remove(int val) {
if(m[val].empty()){
return false;
}
int deleteIdx = m[val].top();
m[val].pop();
if(nums.size() - 1 != deleteIdx){
int swapVal = nums.back();
nums[deleteIdx] = swapVal;
m[swapVal].pop();
m[swapVal].push(deleteIdx);
}
nums.pop_back();
return true;
}
int getRandom() {
return nums[rand() % nums.size()];
}
};
注意到优先队列的 push 不是常数级的,为了严格的遵守 O(1) 的时间复杂度,我们将优先队列换成 unordered_set就好
class RandomizedCollection {
public:
RandomizedCollection() {}
bool insert(int val) {
m[val].insert(nums.size());
nums.push_back(val);
return m[val].size() == 1;
}
bool remove(int val) {
if (m[val].empty()) return false;
int idx = *m[val].begin();
m[val].erase(idx);
if (nums.size() - 1 != idx) {
int t = nums.back();
nums[idx] = t;
m[t].erase(nums.size() - 1);
m[t].insert(idx);
}
nums.pop_back();
return true;
}
int getRandom() {
return nums[rand() % nums.size()];
}
private:
vector<int> nums;
unordered_map<int, unordered_set<int>> m;
};