本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……
专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:
- Tag:介绍本题牵涉到的知识点、数据结构;
- 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
- 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
- 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
- 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。
【变长数组】【哈希表】【设计数据结构】
面试经典150 | 380. O(1) 时间插入、删除和获取随机元素
这是一道设计题,设计一个 RandomizedSet
,可以实现 O ( 1 ) O(1) O(1) 时间的插入、删除以及获取随机元素的功能。具体要求:
RandomizedSet()
:用来初始化 RandomizedSet
对象;bool insert(int val)
:当元素 val
不存在时,向集合中插入该项,并返回 true
;否则,返回 false
。bool remove(int val)
:当元素 val
存在时,从集合中移除该项,并返回 true
;否则,返回 false
。int getRandom()
:随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。对于题目要求的 O ( 1 ) O(1) O(1) 时间复杂度的插入和获取随机随机元素的要求,可以使用数组这种基础的数据结构,考虑到会让数组中加入新的元素,因此使用可变长数组,在 C++ \texttt{C++} C++ 中一种典型的可变长数组就是 vector \texttt{vector} vector。于是需要维护一个可变长数组 nums
来存放元素。
对于插入 val
这个成员方法,首先需要判断 nums
中是否已经有 val
的存在,在数组中查找一个元素是否出现的方法是枚举,时间复杂度为 O ( n ) O(n) O(n),无法做到 O ( 1 ) O(1) O(1) 的时间复杂度,因此好需要一个额外的数据结构来存储元素,并且获取指定元素的时间复杂度还要是 O ( 1 ) O(1) O(1),可是使用哈希表,于是维护一个哈希表键为 val
,值为 val
在数组 nums
中的下标的哈希表 idxs
,之所以这样设定值,我们将在 O ( 1 ) O(1) O(1) 的删除操作中进行解释。
于是,在 bool insert(int val)
这个成员方法中:
idx.count(val)
判断是否已经插入过 val
,如果已经插入过 val
直接返回 false
,否则继续向下执行;nums
的末尾加入 val
,并更新哈希表,即 idxs[val] = n
, n n n 为加入新元素之前的 nums
的长度,也是加入 val
的 nums
中的下标;true
。实现 O ( 1 ) O(1) O(1) 删除 val
的情况稍微复杂一些。首先,需要定位到 val
在数组中的位置,如果不存在 val
直接返回 true
。否则,利用 nums
数组的最后一个元素 last
覆盖掉 val
实现删除操作,最后更新 last
在数组中的新的下标。通过哈希表定位 val
在数组中的位置的时间复杂度为 O ( 1 ) O(1) O(1),利用 last
覆盖 val
的时间复杂度也为 O ( 1 ) O(1) O(1),更新 last
在数组中的新的下标时间复杂度还是 O ( 1 ) O(1) O(1),因此删除操作的时间复杂度为 O ( 1 ) O(1) O(1)。
于是,在 bool remove(int val)
这个成员方法中:
idx.count(val)
判断是否存放 val
,如果不存在 val
直接返回 false
,否则继续向下执行;idxs[val]
获得 val
在数组中的下标 idx
,利用 nums.back()
取出数组中的最后一个元素 last
并覆盖掉 val
的操作为 nums[idx] = last
,还要更新 idxs[last] = idx
,并且 nums.pop_back()
,idxs.erase(val)
;true
。实现 O ( 1 ) O(1) O(1) 时间复杂度下获取随机数就很简单了,我们利用时间随机数种子获取 nums
数组的随机下标,返回随机下标对应的元素即可。
我们在 RandomizedSet()
这个构造函数中,初始化时间随机化种子 srand((unsigned)time(NULL));
。
在 int getRandom()
成员方法中,通过 rand() % nums.size()
获取数组 nums
的随机下标 randomIdx
,然后返回 nums[randomIdx]
。
实现代码
class RandomizedSet {
private:
vector<int> nums;
unordered_map<int, int> idxs;
public:
RandomizedSet() {
srand((unsigned)time(NULL));
}
bool insert(int val) {
if (idxs.count(val)) return false;
int n = nums.size();
nums.push_back(val);
idxs[val] = n;
return true;
}
bool remove(int val) {
if (!idxs.count(val)) return false;
int last = nums.back();
int idx = idxs[val];
nums[idx] = last;
idxs[last] = idx;
nums.pop_back();
idxs.erase(val);
return true;
}
int getRandom() {
int randomIdx = rand() % nums.size();
return nums[randomIdx];
}
};
/**
* 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();
*/
复杂度分析
时间复杂度: O ( 1 ) O(1) O(1),插入、删除以及获取随机数的时间复杂度都是 O ( 1 ) O(1) O(1)。
空间复杂度: O ( n ) O(n) O(n), n n n 为集合中元素的个数,存储元素的数组和哈希表均需要 O ( n ) O(n) O(n) 的空间。
如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 。
如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。
最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 哦。