380. O(1) 时间插入、删除和获取随机元素 - 力扣(LeetCode)
问题描述:
实现RandomizedSet
类:
RandomizedSet()
初始化RandomizedSet
对象bool insert(int val)
当元素val
不存在时,向集合中插入该项,并返回true
;否则,返回false
。bool remove(int val)
当元素val
存在时,从集合中移除该项,并返回true
;否则,返回false
。int getRandom()
随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1)
。
解题思路:
动态数组可以实现O(1)获取指定索引的元素,但不能实现O(1)的判元素存在,O(1)的插入删除,哈希表可以实现后面的O(1)判元素存在,插入删除,但是无法基于索引获取元素,因为哈希表是无序的,要实现题目要求,就要将哈希表和动态数组结合。
使用动态数组存储元素,哈希表存储元素和动态数组中的元素索引,需要获取随机值时使用随机索引从动态数组中取值即可,需要插入删除时使用哈希表获取元素索引再去动态数组中插入删除即可。
但是由于动态数组在中间增删元素时,会改变后续元素的索引,所以此时哈希表中存储的那些元素的索引就不对了,想要维持静态索引的不变,就只能删除尾部元素,尾部元素的增删不会影响前面元素的索引变化,所以采用了交换法,这样哈希表中维护的元素位置信息就能和动态数组一直对上了,保证哈希表维护的位置信息的有效性。
该问题的核心在于:
在移除元素时,使用的是交换覆盖而不是直接移除。这样保证时间复杂度为O(1),同样能够保证删除之后动态数组中的元素的索引不变化。
代码如下:
class RandomizedSet {//随机集合
// 使用哈希表和动态数组设计完成
private HashMap<Integer,Integer> randomMap;
private List<Integer> randomList;
private Random random;//随机数对象
public RandomizedSet() {
this.randomMap=new HashMap<>();
this.randomList=new ArrayList<>();
this.random=new Random();
}
public boolean insert(int val) {
if(randomMap.containsKey(val)){
return false;
}else{
randomList.add(val);
randomMap.put(val,randomList.size()-1);
return true;
}
}
public boolean remove(int val) {
if(randomMap.containsKey(val)){//采用交换法,要移除的元素和尾部元素交换位置
Integer idx=randomMap.get(val);
Integer lastEle=randomList.get(randomList.size()-1);
randomList.set(idx,lastEle);//将尾部元素交换到idx处,覆盖掉要移除的元素
randomList.remove(randomList.size()-1);//将多余的尾部元素移除
//更新哈希表中的元素索引信息
randomMap.put(lastEle,idx);//更新lastEle的索引
randomMap.remove(val);//移除val的位置信息
return true;
}else{
return false;
}
}
public int getRandom() {
int randomIdx=random.nextInt(this.randomList.size());//产生一个0~len,左闭右开的随机数
return randomList.get(randomIdx);
}
}
这道题对于交换法的使用很好,正常如果要删除数组中某个下标的元素,可以采用这种交换法,令删除的时间复杂度为O(1)。