Задача LeetCode «380. Вставить Удалить GetRandom O(1)»
Задача
Implement the RandomizedSet class:
RandomizedSet() Initializes the RandomizedSet object.
bool insert(int val) Inserts an item val into the set if not present. Returns true if the item was not present, false otherwise.
bool remove(int val) Removes an item val from the set if present. Returns true if the item was present, false otherwise.
int getRandom() Returns a random element from the current set of elements (it's guaranteed that at least one element exists when this method is called). Each element must have the same probability of being returned.
You must implement the functions of the class such that each function works in average O(1) time complexity.
Первое, что приходит на ум — использовать карту, но тогда возникает вопрос, как сделать GetRandom в O(1). Общее решение — хранить карту и массив/вектор/срез, карту — для вставки, удаления, массив/вектор/срез — для выборки случайного элемента. Так как я пишу на Golang, а там итератор обхода карты начинается со случайного элемента, я решил не усложнять и просто вернуть из итератора первый элемент. И это работает. Но LeetCode не считает это решение правильным. Я удивился, посмотрел другие решения. LeetCode принимает решения даже со сложностью O(n), но не мое
Мой код
type RandomizedSet struct {
items map[int]struct{}
}
func Constructor() RandomizedSet {
return RandomizedSet{
items: make(map[int]struct{}),
}
}
func (this *RandomizedSet) Insert(val int) bool {
if _, ok := this.items[val]; ok {
return false
}
this.items[val] = struct{}{}
return true
}
func (this *RandomizedSet) Remove(val int) bool {
if _, ok := this.items[val]; !ok {
return false
}
delete(this.items, val)
return true
}
func (this *RandomizedSet) GetRandom() int {
for k := range this.items {
return k
}
return -1
}
Leetcode его не принимает. Но он принимает, если GetRandom имеет следующую форму:
func (this *RandomizedSet) GetRandom() int {
n := len(this.items)
randNum := rand.Intn(n)
var counter int
for k := range this.items {
if counter == randNum {
return k
}
counter++
}
return -1
}
Может я чего-то не понимаю? Кто прав — я или LeetCode?
почему не случайно? В исходниках go (runtime/map_noswiss.go) в func mapiterinit есть инициализация итератора случайными значениями: r := uintptr(rand()) it.startBucket = r & BucketMask(h.B) it.offset = uint8(r > > h.B & (abi.OldMapBucketCount - 1))
Вы не можете полагаться на порядок итерации карты ни в чем, включая случайность порядка.
Я не полагаюсь на случайность полного обхода карты. Мне нужен только 1 случайный элемент. А если посмотреть исходные тексты карты (runtime/map_noswiss.go), то итератор начинается со случайного элемента. В чем дело?
Неважно, проходите ли вы по одному ключу или по всей карте, вы пытаетесь полагаться на детали реализации, которые не удовлетворяют вашим требованиям, и даже если это так, не гарантируется стабильность между версиями. «Случайность» предназначена только для того, чтобы эффективно устранить непреднамеренную зависимость от порядка итераций и не более того. Проведение простого теста показывает, что ключи возвращаются первыми с некоторой предвзятостью go.dev/play/p/weQhvwwtJ0q
Ваш случайный код зависит от итерации, возвращающей каждый элемент на карте с более или менее одинаковой вероятностью. Итерация рандомизирована, но для начала она выбирает случайный сегмент (не случайный элемент). Если в одном сегменте карты находится несколько элементов, второй и последующие никогда не будут первыми в итерации. Вы можете увидеть эффект здесь, где я создаю карту с ключами 0...99, а затем запускаю 100 000 итераций по ключам, чтобы найти первый возвращенный элемент. Обычно никогда не выбираются 6 или 7 значений. См. ссылку на игровую площадку
package main
import "fmt"
const N = 100
func main() {
m := map[int]struct{}{}
for i := range N {
m[i] = struct{}{}
}
first := map[int]bool{}
for range N * 1000 {
for k := range m {
first[k] = true
break
}
}
for i := range N {
if !first[i] {
fmt.Println(i)
}
}
}
Спасибо. В то же время я обнаружил форму «for i := range <CONST> {...}» и «for range <CONST> {...}». Совсем for писать неудобно, а после Kotlin я пропустил "repeat(N) { ... }" и "for i in 0..N { ...}". Теперь я нашел замену.
Обход карты не случаен. Он детерминирован, но порядок произволен. Он не меняется от одной итерации к другой, если вы не измените карту.