题目来源:LeetCode 381: O(1) 时间插入、删除和获取随机元素 - 允许重复
问题抽象: 设计一个支持 重复元素 的数据结构,要求在 O(1) 平均时间复杂度 内完成以下操作:
-
功能定义:
insert(val)
:向集合插入元素val
(允许重复);remove(val)
:从集合中移除一个val
(若存在多个则移除一个;若不存在则忽略);getRandom()
:等概率随机返回 集合中的一个元素(每个元素实例独立参与随机,如[1,1,2]
中1
的概率为2/3
)。
-
操作约束:
insert
和remove
平均时间复杂度 O(1);getRandom
严格 O(1)(需直接通过索引访问);- 空间复杂度 O(n)(
n
为元素总数,含重复项)。
-
数据结构要求:
- 动态数组:存储所有元素实例(允许重复),支持
O(1)
随机访问; - 哈希映射:键为元素值,值为该元素在数组中的 索引集合(如
1: {0,2}
); - 索引交换技巧:
remove
时用数组末尾元素覆盖待删位置,更新索引映射(避免移动元素);
- 动态数组:存储所有元素实例(允许重复),支持
-
边界处理:
- 空集合调用
getRandom
→ 抛出异常或返回-1
(题目指定); - 删除不存在的值 → 返回
false
(操作无效); - 插入重复值 → 数组追加新元素,索引集合更新;
- 特殊用例:
- 插入
[1,1,2]
后,getRandom
返回1
的概率为2/3
; - 删除
1
后,集合为[2,1]
(末尾1
覆盖被删位置); - 删除最后一个元素 → 直接移除并清空索引。
- 插入
- 空集合调用
输入:通过方法调用操作(val
为整数)。
输出:
insert
:返回bool
(是否成功插入,通常恒为true
);remove
:返回bool
(是否成功移除);getRandom
:返回随机元素值(整数)。
解题思路
本题要求设计一个数据结构,支持在平均 O(1) 时间内完成插入、删除和获取随机元素操作,且允许元素重复。核心挑战在于如何在 O(1) 时间内删除指定元素(尤其是中间元素)并维护随机访问能力。
核心设计
- 动态数组(ArrayList):存储所有元素,支持 O(1) 的随机访问(通过索引)。
- 哈希表(HashMap):键为元素值,值为该元素在动态数组中的所有位置索引(使用 HashSet 存储,实现 O(1) 的插入和删除)。
- 删除策略:删除元素时,将待删除元素与数组末尾元素交换,然后删除末尾元素,避免移动其他元素,保持 O(1) 时间复杂度。
操作细节
- 插入(insert):
- 将元素添加到动态数组末尾。
- 在哈希表中记录该元素的新位置(即数组末尾索引)。
- 返回布尔值:若元素首次插入返回 true,重复插入返回 false。
- 删除(remove):
- 从哈希表中获取待删除元素的位置集合,移除一个位置(任意)。
- 将数组末尾元素移动到待删除元素的位置。
- 更新原末尾元素的位置集合:移除原末尾位置,添加新位置(若移动位置非原末尾)。
- 删除数组末尾元素。
- 若元素位置集合为空,从哈希表移除该元素。
- 获取随机元素(getRandom):生成随机索引,返回数组中对应位置的元素。
代码实现(Java版)🔥点击下载源码
class RandomizedCollection {
private List<Integer> list; // 存储元素的动态数组
private Map<Integer, Set<Integer>> indexMap; // 元素值到位置索引集合的映射
private Random rand; // 随机数生成器
public RandomizedCollection() {
list = new ArrayList<>();
indexMap = new HashMap<>();
rand = new Random();
}
public boolean insert(int val) {
list.add(val); // 元素添加到数组末尾
Set<Integer> positions = indexMap.getOrDefault(val, new HashSet<>());
positions.add(list.size() - 1); // 记录新位置
indexMap.put(val, positions);
return positions.size() == 1; // 首次插入返回true,重复插入返回false
}
public boolean remove(int val) {
if (!indexMap.containsKey(val) || indexMap.get(val).isEmpty()) {
return false; // 元素不存在,删除失败
}
Set<Integer> positions = indexMap.get(val);
int pos = positions.iterator().next(); // 获取任意一个位置
positions.remove(pos); // 从集合中移除该位置
int lastVal = list.get(list.size() - 1); // 获取末尾元素
list.set(pos, lastVal); // 将末尾元素移动到待删除位置
Set<Integer> lastSet = indexMap.get(lastVal);
lastSet.remove(list.size() - 1); // 移除原末尾位置
if (pos < list.size() - 1) { // 若移动位置非原末尾,则添加新位置
lastSet.add(pos);
}
list.remove(list.size() - 1); // 删除末尾元素
if (positions.isEmpty()) {
indexMap.remove(val); // 若位置集合为空,移除该元素
}
return true;
}
public int getRandom() {
return list.get(rand.nextInt(list.size())); // 随机索引访问
}
}
代码说明
-
数据结构:
list
:动态数组,存储所有元素,支持 O(1) 随机访问。indexMap
:哈希表,键为元素值,值为该元素在list
中的位置集合(HashSet
实现 O(1) 的增删)。rand
:随机数生成器,用于生成随机索引。
-
插入操作(insert):
- 将元素添加到
list
末尾(O(1))。 - 在
indexMap
中记录新位置(O(1))。 - 返回值:若元素首次插入返回
true
,重复插入返回false
。
- 将元素添加到
-
删除操作(remove):
- 检查元素是否存在(O(1))。
- 获取并移除该元素的任意位置(O(1))。
- 将末尾元素移动到待删除位置(O(1))。
- 更新原末尾元素的位置集合(O(1))。
- 删除末尾元素(O(1))。
- 若元素无其他位置,从
indexMap
中移除(O(1))。
-
获取随机元素(getRandom):
- 生成随机索引(O(1))。
- 返回
list
中对应位置的元素(O(1))。