🎲
[leetCode] Top Interview 150: Insert Delete GetRandom O(1)
リンク
概要
-
RandomizedSet
クラスを実装していく。-
bool insert(int val)
: データを挿入する。成功ならtrue
, ダメならfalse
を返す。 -
bool remove(int val)
: データを削除する。成功ならtrue
, ダメならfalse
を返す。 -
int getRandom()
: ランダムにひとつデータを取得して返す。
-
- 各関数の平均計算量が
になるように実装する。O(1)
解法
ぱっと思いつくのは、 HashSet
クラスを使用した実装。
class RandomizedSet {
private Set<Integer> set;
public RandomizedSet() {
set = new HashSet<>();
}
public boolean insert(int val) {
return this.set.add(val);
}
public boolean remove(int val) {
return this.set.remove(val);
}
public int getRandom() {
int size = this.set.size();
int index = (int) (Math.random() * size);
Iterator<Integer> iterator = this.set.iterator();
while (index-- > 0) {
iterator.next();
}
return iterator.next();
}
}
これでも一応通る...けど、getRandom()
の処理内でwhile
を使用しているため、平均計算量が
前提として、 HashSet
にはインデックス参照による要素の取得がない。 そのため、乱数で適当にインデックスを決めて取得、みたいなことが難しい。
この問題を解決するためには、ArrayList
で要素を管理しつつ、HashMap
で要素の位置をメモし、検索処理を軽量化する必要がある。ついでに、HashMap.containsKey()
で要素の重複をチェックすれば、かんたんにHashSet
の性質を引き継ぐことができる。
これを踏まえて実装したのが以下のコード。
import java.util.*;
class RandomizedSet {
private Map<Integer, Integer> map;
private List<Integer> list;
private Random random;
public RandomizedSet() {
map = new HashMap<>();
list = new ArrayList<>();
random = new Random();
}
public boolean insert(int val) {
if (map.containsKey(val)) {
return false;
}
list.add(val);
map.put(val, list.size() - 1);
return true;
}
public boolean remove(int val) {
if (!map.containsKey(val)) {
return false;
}
int indexToRemove = map.get(val);
int lastElement = list.get(list.size() - 1);
list.set(indexToRemove, lastElement);
map.put(lastElement, indexToRemove);
list.removeLast();
map.remove(val);
return true;
}
public int getRandom() {
return list.get(random.nextInt(list.size()));
}
}
注意点として、ArrayList.remove()
の計算量は
これは、 Java の公式ドキュメントからも記述が確認できる。
例外として、末尾要素の削除は
おわりに
普段脳死でコレクションを使っていたので「HashSet
でアレコレすればいけるやろw」と思っていたらハマりました。学び。
Discussion