381. O(1) 时间插入、删除和获取随机元素 - 允许重复
381. O(1) 时间插入、删除和获取随机元素 - 允许重复
🔴 Hard 🔖 设计
数组
哈希表
数学
随机化
🔗 力扣
LeetCode
题目
RandomizedCollection
is a data structure that contains a collection of numbers, possibly duplicates (i.e., a multiset). It should support inserting and removing specific elements and also reporting a random element.
Implement the RandomizedCollection
class:
RandomizedCollection()
Initializes the emptyRandomizedCollection
object.bool insert(int val)
Inserts an itemval
into the multiset, even if the item is already present. Returnstrue
if the item is not present,false
otherwise.bool remove(int val)
Removes an itemval
from the multiset if present. Returnstrue
if the item is present,false
otherwise. Note that ifval
has multiple occurrences in the multiset, we only remove one of them.int getRandom()
Returns a random element from the current multiset of elements. The probability of each element being returned is linearly related to the number of the same values the multiset contains.
You must implement the functions of the class such that each function works on average O(1)
time complexity.
Note: The test cases are generated such that getRandom
will only be called if there is at least one item in the RandomizedCollection
.
Example 1:
Input
["RandomizedCollection", "insert", "insert", "insert", "getRandom", "remove", "getRandom"]
[[], [1], [1], [2], [], [1], []]
Output
[null, true, false, true, 2, true, 1]
Explanation
RandomizedCollection randomizedCollection = new RandomizedCollection();
randomizedCollection.insert(1); // return true since the collection does not contain 1.
// Inserts 1 into the collection.
randomizedCollection.insert(1); // return false since the collection contains 1.
// Inserts another 1 into the collection. Collection now contains [1,1].
randomizedCollection.insert(2); // return true since the collection does not contain 2.
// Inserts 2 into the collection. Collection now contains [1,1,2].
randomizedCollection.getRandom(); // getRandom should:
// - return 1 with probability 2/3, or
// - return 2 with probability 1/3.
randomizedCollection.remove(1); // return true since the collection contains 1.
// Removes 1 from the collection. Collection now contains [1,2].
randomizedCollection.getRandom(); // getRandom should return 1 or 2, both equally likely.
Constraints:
-2^31 <= val <= 2^31 - 1
- At most
2 * 10^5
calls in total will be made toinsert
,remove
, andgetRandom
. - There will be at least one element in the data structure when
getRandom
is called.
题目大意
RandomizedCollection
是一种包含数字集合(可能是重复的)的数据结构。它应该支持插入和删除特定元素,以及删除随机元素。
实现 RandomizedCollection
类:
RandomizedCollection()
初始化空的RandomizedCollection
对象。bool insert(int val)
将一个val
项插入到集合中,即使该项已经存在。如果该项不存在,则返回true
,否则返回false
。bool remove(int val)
如果存在,从集合中移除一个val
项。如果该项存在,则返回true
,否则返回false
。注意,如果val
在集合中出现多次,我们只删除其中一个。int getRandom()
从当前的多个元素集合中返回一个随机元素。每个元素被返回的概率与集合中包含的相同值的数量 线性相关 。
您必须实现类的函数,使每个函数的 平均 时间复杂度为 O(1)
。
注意: 生成测试用例时,只有在 RandomizedCollection
中 至少有一项 时,才会调用 getRandom
。
示例 1:
输入
["RandomizedCollection", "insert", "insert", "insert", "getRandom", "remove", "getRandom"]
[[], [1], [1], [2], [], [1], []]
输出
[null, true, false, true, 2, true, 1]
解释
RandomizedCollection collection = new RandomizedCollection();// 初始化一个空的集合。
collection.insert(1); // 返回 true,因为集合不包含 1。
// 将 1 插入到集合中。
collection.insert(1); // 返回 false,因为集合包含 1。
// 将另一个 1 插入到集合中。集合现在包含 [1,1]。
collection.insert(2); // 返回 true,因为集合不包含 2。
// 将 2 插入到集合中。集合现在包含 [1,1,2]。
collection.getRandom(); // getRandom 应当:
// 有 2/3 的概率返回 1,
// 1/3 的概率返回 2。
collection.remove(1); // 返回 true,因为集合包含 1。
// 从集合中移除 1。集合现在包含 [1,2]。
collection.getRandom(); // getRandom 应该返回 1 或 2,两者的可能性相同。
提示:
-2^31 <= val <= 2^31 - 1
insert
,remove
和getRandom
最多 总共 被调用2 * 10^5
次- 当调用
getRandom
时,数据结构中 至少有一个 元素
解题思路
我们需要支持 O(1) 的插入、删除和随机访问,因此采用:
- 数组
elements
:用于存储所有插入的元素,方便getRandom()
通过索引 O(1) 获取随机元素。 - 哈希表
indicesMap
(Map<number, Set<number>>
):- key: 存储元素值
- value: 该值在数组
elements
中的所有索引集合(Set
)。 - 为什么用
Set
? 允许相同的val
多次插入,并且删除操作仍然保持 O(1) 复杂度。
insert(val)
1. - 先检查
val
是否已经存在于indicesMap
。 - 将
val
添加到elements
数组的 末尾。 - 在
indicesMap
里 记录索引:- 如果
val
不存在,创建Set
存储索引。 - 如果
val
已存在,直接将新索引加入Set
。
- 如果
- 返回
val
是否是第一次插入(true
或false
)。
- 时间复杂度:O(1),因为
数组 push
和Map 操作
皆为 O(1)。
remove(val)
2. - 先检查
val
是否存在于indicesMap
,如果不存在直接返回false
。 - 找到
val
在数组中的某个索引removeIndex
。 - 获取
elements
最后一个元素lastElement
,准备 将其移动到removeIndex
位置(避免数组splice
操作,保证 O(1))。 - 替换
val
elements[removeIndex] = lastElement
(将最后一个元素移动到val
的位置)elements.pop()
删除最后一个元素
- 更新
indicesMap
- 删除
removeIndex
,如果val
没有索引了,从Map
中删除。 - 更新
lastElement
的索引(如果lastElement
不是val
本身)。
- 删除
- 返回
true
,表示成功删除。
- 时间复杂度:O(1),因为:
- 获取索引是 O(1) (
Set.values().next().value
) - 替换索引是 O(1)
elements.pop()
是 O(1)Map
操作是 O(1)
- 获取索引是 O(1) (
getRandom()
3. - 生成
[0, elements.length - 1]
之间的随机索引。 - 返回
elements[randomIndex]
。
- 时间复杂度:O(1),因为数组随机访问是 O(1)。
复杂度分析
- 时间复杂度:
O(1)
,insert
,remove
和getRandom
都是O(1)
时间复杂度。 - 空间复杂度:
O(n)
,使用了一个数组和一个哈希表存储元素及其索引。
代码
class RandomizedCollection {
constructor() {
this.elements = []; // 存储所有插入的元素
this.indicesMap = new Map(); // Map<number, Set<number>>,存储每个值的索引集合
}
/**
* 插入一个值到集合中
* @param {number} val
* @return {boolean} 是否是第一次插入
*/
insert(val) {
const exists = this.indicesMap.has(val); // 记录 val 是否已经存在
this.elements.push(val); // 将 val 添加到数组末尾
// 在哈希表中记录 val 对应的索引
if (!exists) {
this.indicesMap.set(val, new Set());
}
this.indicesMap.get(val).add(this.elements.length - 1);
return !exists;
}
/**
* 从集合中删除一个值
* @param {number} val
* @return {boolean} 是否删除成功
*/
remove(val) {
if (!this.indicesMap.has(val)) return false; // 若 val 不存在,直接返回 false
const lastIndex = this.elements.length - 1; // 获取数组最后一个元素的索引
const valIndices = this.indicesMap.get(val); // 获取 val 所在的索引集合
const removeIndex = valIndices.values().next().value; // 取出 val 在数组中的某个索引
const lastElement = this.elements[lastIndex]; // 获取数组的最后一个元素
this.elements[removeIndex] = lastElement; // 用最后一个元素覆盖待删除元素
this.elements.pop(); // 删除数组最后一个元素
// 更新 val 的索引集合
valIndices.delete(removeIndex);
if (valIndices.size === 0) this.indicesMap.delete(val); // 若 val 没有索引了,从 Map 中删除
// 若被替换的元素 lastElement 不是 val,则需要更新 lastElement 的索引
if (removeIndex !== lastIndex) {
const lastIndices = this.indicesMap.get(lastElement);
lastIndices.delete(lastIndex);
lastIndices.add(removeIndex); // 更新 lastElement 在数组中的位置
}
return true;
}
/**
* 随机获取一个元素
* @return {number} 随机返回集合中的一个元素
*/
getRandom() {
const randomIndex = Math.floor(Math.random() * this.elements.length);
return this.elements[randomIndex];
}
}
/**
* Your RandomizedCollection object will be instantiated and called as such:
* var obj = new RandomizedCollection()
* var param_1 = obj.insert(val)
* var param_2 = obj.remove(val)
* var param_3 = obj.getRandom()
*/
相关题目
题号 | 标题 | 题解 | 标签 | 难度 | 力扣 |
---|---|---|---|---|---|
380 | O(1) 时间插入、删除和获取随机元素 | [✓] | 设计 数组 哈希表 2+ | 🟠 | 🀄️ 🔗 |