共计 2336 个字符,预计需要花费 6 分钟才能阅读完成。
题目要求
Design a data structure that supports all following operations in average O(1) time.
insert(val): Inserts an item val to the set if not already present.
remove(val): Removes an item val from the set if present.
getRandom: Returns a random element from current set of elements. Each element must have the same probability of being returned.
Example:
// Init an empty set.
RandomizedSet randomSet = new RandomizedSet();
// Inserts 1 to the set. Returns true as 1 was inserted successfully.
randomSet.insert(1);
// Returns false as 2 does not exist in the set.
randomSet.remove(2);
// Inserts 2 to the set, returns true. Set now contains [1,2].
randomSet.insert(2);
// getRandom should return either 1 or 2 randomly.
randomSet.getRandom();
// Removes 1 from the set, returns true. Set now contains [2].
randomSet.remove(1);
// 2 was already in the set, so return false.
randomSet.insert(2);
// Since 2 is the only number in the set, getRandom always return 2.
randomSet.getRandom();
设计一个数据结构,使得能够在 O(1) 的时间复杂度中插入数字,删除数字,以及随机获取一个数字。要求所有的数字都能够被等概率的随机出来。
思路和代码
其实有几个思路入手:
如何实现 O(1) 的插入
这里数字的插入还需要能够去重,即需要首先判断该数字是否已经存在,已经存在的话就不执行任何插入操作。如果底层是一个一般的数组,我们知道查询的时间复杂度为 O(n),明显不满足题目的意思。一个有序的数组能够将查询的时间复杂度下降到 O(lgn),但是这依然不满足条件 1,而且也无法做到所有的元素被等概率的查询出来,因为每插入一个元素都将改动之前元素的位置。而唯一能够做到 O(1) 时间查询的只有一个数据结构,即 hash。因此,使用 hash 来查询时不可避免的。
如何实现 O(1) 的删除
这个其实是一个很经典的问题了,只要能够利用 hash 在 O(1) 的时间内找到这个数字的位置,就有两种方法来实现 O(1) 的删除,一个是利用伪删除,即每一个位置都对应一个状态为,将状态位社会为已经删除即可,还有一种就更有意思,就是将被删除位替换为数组最后一位的值,然后只需要删除最后一位就行。这种删除就无需将删除位右侧的元素全部左移造成 O(n) 的时间复杂度。这里我们采用的是第二种方法。
如何实现 O(1) 的随机查询
这个其实就是强调一点,我们需要维持原有的插入顺序,从而保证各个元素等概率被随机。
综上所述,我们底层需要两种数据结构,一个 hashmap 来支持 O(1) 的查询,以及一个 list 来支持随机数的获取。代码实现如下:
public class InsertDeleteGetRandom_380 {
private List<Integer> list;
private Map<Integer, Integer> hash;
public InsertDeleteGetRandom_380() {
list = new ArrayList<Integer>();
hash = new HashMap<Integer, Integer>();
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if(hash.containsKey(val)) {
return false;
}
list.add(val);
hash.put(val, list.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(!hash.containsKey(val)){
return false;
}
int position = hash.get(val);
if(position != list.size()-1) {
int last = list.get(list.size()-1);
list.set(position, last);
hash.put(last, position);
}
list.remove(list.size()-1);
hash.remove(val);
return true;
}
/** Get a random element from the set. */
public int getRandom() {
int position = (int)Math.floor((Math.random() * list.size()));
return list.get(position);
}
}