一、题目描述
实现RandomizedSet
类:
RandomizedSet()
初始化RandomizedSet
对象bool insert(int val)
当元素val
不存在时,向集合中插入该项,并返回true
;否则,返回false
。bool remove(int val)
当元素val
存在时,从集合中移除该项,并返回true
;否则,返回false
。int getRandom()
随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1)
。
示例:
输入 ["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"] [[], [1], [2], [2], [], [1], [2], []] 输出
[null, true, false, true, 2, true, false, 2]
解释 RandomizedSet randomizedSet = new RandomizedSet(); randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。 randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。 randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。 randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。 randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。 randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。 randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
提示:
-231 <= val <= 231 - 1
- 最多调用
insert
、remove
和getRandom
函数2 *
105
次 - 在调用
getRandom
方法时,数据结构中 至少存在一个 元素。
二、题目分析
因为题目对时间复杂度做出了O(1)的限制,所以自然排除遍历的想法,于是想到用Map的索引机制,而哈希表的哈希值就可以实现O(1)的查找和删除。简单点介绍原理就是hashmap是基于数组+链表+红黑树。
这里借用别人的数组+链表的图来加以理解,在每次存储数组时,都会生成一个哈希值,系统会更具这个哈希值来进行查找和定位,从而实现O(1)的时间复杂度,对于产生的相同的哈希值,就可以通过链表的形式链接,不过对于哈希函数来说,这种规模的数据发生哈希冲突的概率基本没有影响,所以可以当作O(1)来处理。
解决逻辑
- 数据结构选择:
- 使用一个
ArrayList<Integer>
来存储集合中的元素,这样可以支持 O(1) 时间复杂度的随机访问。 - 使用一个
HashMap<Integer, Integer>
来存储元素及其在ArrayList
中的索引,这样可以支持 O(1) 时间复杂度的查找和删除操作。
- 使用一个
- 插入操作 (
insert
):- 首先检查元素是否已经存在于
HashMap
中。如果存在,返回false
。 - 如果不存在,将元素添加到
ArrayList
的末尾,并在HashMap
中记录该元素及其索引,返回true
。
- 首先检查元素是否已经存在于
- 删除操作 (
remove
):- 首先检查元素是否存在于
HashMap
中。如果不存在,返回false
。 - 如果存在,获取该元素在
ArrayList
中的索引,然后将ArrayList
中的最后一个元素移动到该索引位置,并更新HashMap
中最后一个元素的索引。 - 最后,从
ArrayList
中移除最后一个元素,并从HashMap
中移除该元素,返回true
。
- 首先检查元素是否存在于
- 随机获取操作 (
getRandom
):- 使用
Random
类生成一个随机索引,然后从ArrayList
中获取该索引对应的元素并返回。
- 使用
三、代码实现
class RandomizedSet {
List<Integer> nums;
Map<Integer, Integer> indices;
Random random;
public RandomizedSet() {
nums = new ArrayList<Integer>();
indices = new HashMap<Integer, Integer>();
random = new Random();
}
public boolean insert(int val) {
if (indices.containsKey(val)) {
return false;
}
int index = nums.size();
nums.add(val);
indices.put(val, index);
return true;
}
public boolean remove(int val) {
if (!indices.containsKey(val)) {
return false;
}
int index = indices.get(val);
int last = nums.get(nums.size() - 1);
nums.set(index, last);
indices.put(last, index);
nums.remove(nums.size() - 1);
indices.remove(val);
return true;
}
public int getRandom() {
int randomIndex = random.nextInt(nums.size());
return nums.get(randomIndex);
}
}
四、总结
通过结合 ArrayList
和 HashMap
,能够实现 O(1) 时间复杂度的插入、删除和随机获取操作。在删除操作中,通过将最后一个元素移动到要删除的元素的位置,可以避免在 ArrayList
中进行大量的元素移动,从而保持 O(1) 的时间复杂度。使用 Random
类生成随机索引,可以保证每个元素被随机获取的概率相等(看评论好像是基于出现答案的概率是否位于阈值空间内来判断是否实现等概率分布)。这道题还是平常没接触过的,第一次写的时候也是一点思路没有,也是一边学习一边写个大概,如果有不足的地方还是需要指正。