1. 题目背景与需求分析
LeetCode 381题要求我们设计一个支持重复元素的数据结构,能够在O(1)时间复杂度内完成插入、删除和获取随机元素的操作。这个题目看似简单,但要在满足时间复杂度要求的同时处理重复元素,需要巧妙的数据结构组合。
1.1 核心需求拆解
这个数据结构需要满足三个核心操作:
- insert(val):插入元素val到集合中
- remove(val):从集合中移除元素val
- getRandom():随机返回集合中的一个元素
特别需要注意的是:
- 集合允许包含重复元素
- 随机获取元素时,每个元素被选中的概率应该与其在集合中的出现次数成正比
- 所有操作的时间复杂度必须是O(1)
1.2 为什么这是个有挑战的问题?
常规的数据结构很难同时满足这些要求:
- 使用纯ArrayList:随机访问是O(1),但删除特定元素需要O(n)时间
- 使用纯HashSet:插入和删除是O(1),但无法实现随机访问
- 使用LinkedHashSet:可以保持顺序,但无法处理重复元素
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构设计与原理
2.1 组合数据结构方案
经过分析,我们需要组合使用三种数据结构:
2.1.1 ArrayList:存储所有元素
- 提供O(1)的随机访问能力
- 支持在末尾快速插入和删除
- 存储所有元素的实际值,包括重复项
2.1.2 HashMap<Integer, LinkedHashSet>:维护值到索引的映射
- 键是元素值
- 值是该元素在ArrayList中所有出现位置的索引集合
- 使用LinkedHashSet保证O(1)的增删和有序性
2.1.3 Random:生成随机数
- 用于实现getRandom()操作
- 在ArrayList的范围内生成随机索引
2.2 为什么选择LinkedHashSet?
相比普通HashSet,LinkedHashSet有以下优势:
- 保持插入顺序,使得我们可以方便地获取"第一个"或"最后一个"索引
- 迭代时行为可预测,便于调试和边界情况处理
- 虽然理论时间复杂度相同,但实际性能更稳定
3. 完整代码实现与解析
3.1 类定义与初始化
java复制import java.util.*;
class RandomizedCollection {
private List<Integer> nums; // 存储所有元素
private Map<Integer, LinkedHashSet<Integer>> valToIndices; // 值到索引的映射
private Random random; // 随机数生成器
public RandomizedCollection() {
nums = new ArrayList<>();
valToIndices = new HashMap<>();
random = new Random();
}
}
3.2 插入操作实现
java复制public boolean insert(int val) {
// 获取或创建该值的索引集合
LinkedHashSet<Integer> indices = valToIndices.computeIfAbsent(val,
k -> new LinkedHashSet<>());
// 添加新索引(当前列表大小就是新元素的索引)
indices.add(nums.size());
// 添加元素到列表末尾
nums.add(val);
// 返回是否是新插入的元素(之前不存在)
return indi
