【20201031 每日一题】刷题使我快乐。200+题解已收录至我的GitHub,欢迎关注。
今天做的是 381. O(1) 时间插入、删除和获取随机元素 - 允许重复,在做这道题之前,我们先来看一下这道题的低级版:380. 常数时间插入、删除和获取随机元素 。知道了 0380 怎么做,在这个基础上把键值对的值改成Set<Integer>就好办了~
0380 常数时间插入、删除和获取随机元素
380. 常数时间插入、删除和获取随机元素
难度中等
设计一个支持在 平均 时间复杂度 O(1) 下,执行以下操作的数据结构。
insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率被返回。
示例 :
// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();
// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);
// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);
// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);
// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();
// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);
// 2 已在集合中,所以返回 false 。
randomSet.insert(2);
// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();
看到这题,就觉得似曾相识,以前在上公开课,写作业的时候不就写过这一题嘛?
见 题目,代码。
核心思想是,用数组存储所有元素,元素排放顺序无所谓,反正如果删除的话就和尾部元素换个位置就行了。
这里也是一样。
- int size记录所有元素数量。
- int[] nums 存储所有元素,初始化成较长的数组--目的是避免扩容(一般来说题目应该给出对RandomizedSet的操作次数,但这里没给)。添加元素的时候在nums的尾部添加,删除元素时和尾部元素交换,size--。
- Map<Integer,Integer> map 记录 (内容nums[i],下标i)键值对。
class RandomizedSet {
Map<Integer, Integer> map=new HashMap<>();
int[] nums=new int[10000];
int size=0;
/** Initialize your data structure here. */
public RandomizedSet() {
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if(map.containsKey(val)) return false;
nums[size++]=val;
map.put(val, size-1);
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
public boolean remove(int val) {
if(!map.containsKey(val)) return false;
int pos=map.remove(val);
if(pos!=size-1){
nums[pos]=nums[size-1];
map.put(nums[pos], pos);
}
size--;
return true;
}
/** Get a random element from the set. */
public int getRandom() {
return nums[(int) (Math.random()*size)];
}
}
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet obj = new RandomizedSet();
* boolean param_1 = obj.insert(val);
* boolean param_2 = obj.remove(val);
* int param_3 = obj.getRandom();
*/
0381 O(1)时间插入、删除和获取随机元素 -允许重复
381. O(1) 时间插入、删除和获取随机元素 - 允许重复
难度困难150
设计一个支持在 平均 时间复杂度 O(1) 下 , 执行以下操作的数据结构。 注意: 允许出现重复元素。insert(val)
:向集合中插入元素 val。remove(val)
:当 val 存在时,从集合中移除一个 val。getRandom
:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。
示例:
// 初始化一个空的集合。
RandomizedCollection collection = new RandomizedCollection();
// 向集合中插入 1 。返回 true 表示集合不包含 1 。
collection.insert(1);
// 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。 collection.insert(1);
// 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。
collection.insert(2);
// getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。
collection.getRandom();
// 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。
collection.remove(1);
// getRandom 应有相同概率返回 1 和 2 。
collection.getRandom();
这道题和上面的区别在于,允许重复值了。那么显然用Integer来存储值在nums中的位置就不现实了,这里就得用Set<Integer>来存储。
这道题,我们用这些数据结构。
- int size 作用同上。
- int[] nums 作用同上。
- Map<Integer, Set<Integer>> map 记录 (内容nums[i],下标i 们)键值对。因为允许重复值,所以同一个元素可能对应多个位置。
class RandomizedCollection {
// Map<Integer, Integer> map=new HashMap<>();
Map<Integer, Set<Integer>> map=new HashMap<>();
int[] nums=new int[10000];
int size=0;
/** Initialize your data structure here. */
public RandomizedCollection() {
}
/** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
public boolean insert(int val) {
boolean flag=true;
if(map.containsKey(val)) flag=false;
nums[size++]=val;
Set<Integer> set=map.getOrDefault(val, new HashSet<Integer>());
set.add(size-1);
map.put(val, set);
return flag;
}
/** Removes a value from the collection. Returns true if the collection contained the specified element. */
public boolean remove(int val) {
if(!map.containsKey(val)) return false;
Iterator<Integer> it=map.get(val).iterator();
int pos=it.next();
map.get(val).remove(pos);
if(pos!=size-1){
nums[pos]=nums[size-1];
Set<Integer> set=map.get(nums[pos]);
set.remove(size-1);
set.add(pos);
map.put(nums[pos], set);
}
if(map.get(val).size()==0) map.remove(val);
size--;
return true;
}
/** Get a random element from the collection. */
public int getRandom() {
return nums[(int) (Math.random()*size)];
}
}
/**
* Your RandomizedCollection object will be instantiated and called as such:
* RandomizedCollection obj = new RandomizedCollection();
* boolean param_1 = obj.insert(val);
* boolean param_2 = obj.remove(val);
* int param_3 = obj.getRandom();
*/
数据结构讨论
Q1:可以用Map<Integer, List<Integer>> 吗?
A1:可以。但是要注意一个地方,remove的时候要转成 Integer,例如:
map.get(val).remove((Integer) pos);
查看List的API,可见
对于Set而言,remove会自动装箱为Integer。但是对于List,如果输入整型变量,那么默认调用的是第一个函数,而不是第二个函数。
这个改为List的代码我贴在GitHub上了,戳 这里 即可查看。速度比Set快1ms(可以忽略)。
Q2:nums可以用List<Integer>代替吗?
A2:当然可以。但是List的坏处就在于,它可能需要扩容多次,比较浪费时间。所以我干脆设计一个超长的nums免去扩容的麻烦。事实上,官方解答 就是用List<Integer>做的,但是速度比数组要慢啊。
欢迎点赞、评论、分享本文并且关注我的GitHub,已收录200+题解。
你所有的互动都是我坚持写下去的动力,谢谢。
以上。