Ответ 1
В основном вы реализуете мультимножество с ограниченным размером, как в количестве элементов (#elements <= m
), так и в допустимом диапазоне для элементов (1 <= elementValue <= n
).
- Поиск:
myCollection.search(x)
→ return True, если x внутри, else False - Вставить:
myCollection.insert(x)
→ добавить ровно один x в коллекцию - Удалить:
myCollection.delete(x)
→ удалить ровно один x из коллекции
Рассмотрим, что произойдет, если вы попытаетесь сохранить 5 дважды, например
myCollection.insert(5)
myCollection.insert(5)
Вот почему вы не можете использовать бит-вектор. Но в нем говорится "единицы" пространства, поэтому разработка вашего метода состояла бы в том, чтобы вести подсчет каждого элемента. Например, у вас может быть [_,_,_,_,1,_,...]
, затем [_,_,_,_,2,_,...]
.
Почему это не работает? Кажется, что это работает отлично, например, если вы вставляете 5, а затем удаляете 5... но что произойдет, если вы делаете .search(5)
в неинициализированном массиве? Вам определенно сказано, что вы не можете его инициализировать, поэтому вы не можете сказать, действительно ли значение, которое вы найдете в этом фрагменте памяти e.g. 24753
, на самом деле означает "существует 24753 экземпляра 5
" или если он мусор.
ПРИМЕЧАНИЕ. Вы должны разрешить себе пространство инициализации O(1)
, или проблема не может быть решена. (В противном случае a .search()
не сможет отличить случайный мусор в вашей памяти от фактических данных, потому что вы всегда можете найти случайный мусор, похожий на фактические данные.) Например, вы можете подумать о наличии логического значения, которое означает "I начали использовать мою память", которую вы инициализируете до False, и установите значение True в тот момент, когда вы начнете писать свои слова памяти m
.
Если вам нужно полное решение, вы можете навести курсор на серый блок, чтобы показать тот, с которым я столкнулся. Это всего лишь несколько строк кода, но доказательства немного длиннее:
СПОЙЛЕР: ПОЛНОЕ РЕШЕНИЕ
Настройка:
Используйте N слов в качестве таблицы рассылки:locationOfCounts[i]
- это массив размера N со значениями в диапазонеlocation=[0,M]
. Это место, где будет храниться счетчикi
, но мы можем доверять только этому значению, если мы можем доказать, что это не мусор. > !
(sidenote: Это эквивалентно массиву указателей, но массив указателей предоставляет вам возможность поиска мусора, поэтому вам придется кодировать эту реализацию с помощью проверок диапазона указателей.)
Чтобы узнать, сколькоi
есть в коллекции, вы можете посмотреть вверху значениеcounts[loc]
. Мы используем M-слова как сами счетчики:counts
- это массив размера N с двумя значениями для каждого элемента. Первое значение - это число, которое представляет, а второе значение - это количество (в диапазоне [1, м]). Например, значение(5,2)
означает, что в коллекции хранятся 2 экземпляра числа5
.
(M слов достаточно места для всех отсчетов. Доказательство. Мы знаем, что никогда не может быть больше, чем M элементов, поэтому в худшем случае мы имеем M counts value = 1. QED)
(Мы также хотим отслеживать только counts >= 1, иначе нам не хватило бы памяти.)
Используйте номер с именемnumberOfCountsStored
, который инициализируется до 0, но обновляется всякий раз, когда изменяется количество типов элементов. Например, это число будет 0 для{}
, 1 для{5:[1 times]}
, 1 для{5:[2 times]}
и 2 для{5:[2 times],6:[4 times]}
.
1 2 3 4 5 6 7 8...
locationOfCounts[<N]: [☠, ☠, ☠, ☠, ☠, 0, 1, ☠, ...]
counts[<M]: [(5,⨯2), (6,⨯4), ☠, ☠, ☠, ☠, ☠, ☠, ☠, ☠..., ☠]
numberOfCountsStored: 2
Ниже мы очищаем детали каждой операции и доказываем, почему это правильно:
Алгоритм:
Существуют две основные идеи: 1) мы никогда не сможем позволить себе читать память, не проверяя, что это не мусор в первую очередь, или если мы это сделаем, мы должны быть в состоянии доказать, что это мусор, 2) нам нужно иметь возможность доказать вO(1)
время инициализации фрагмента памятиcounter
с пробеломO(1)
. Чтобы обойти это, используемое нами пространствоO(1)
numberOfItemsStored
. Каждый раз, когда мы выполняем операцию, мы вернемся к этому номеру, чтобы доказать, что все было правильно (например, см. Ниже). Инвариант представления состоит в том, что мы всегда будем хранить counts вcounts
, идущем слева направо, поэтомуnumberOfItemsStored
всегда будет максимальным индексом массива, который действителен.
.search(e)
- ПроверьтеlocationsOfCounts[e]
. Предположим теперь, что значение правильно инициализировано и может быть доверено. Мы переходим к проверкеcounts[loc]
, но сначала проверяем, была ли инициализированаcounts[loc]
: она инициализируется, если 0 <=loc
<numberOfCountsStored
(если нет, данные являются бессмысленными, поэтому мы возвращаем False). После проверки, мы посмотримcounts[loc]
, который дает нам паруnumber,count
. Еслиnumber
!=e
, мы попали сюда, следуя рандомизированному мусору (бессмысленно), поэтому мы возвращаем False (опять как указано выше)... но если действительноnumber
==e
, это доказывает, что счет (доказательство ★:numberOfCountsStored
является свидетелем того, что этот конкретныйcounts[loc]
действителен, аcounts[loc].number
является свидетелем того, чтоlocationOfCounts[number]
действителен, и, следовательно, наш первоначальный поиск не был мусором.), поэтому мы вернемся Правда.
.insert(e)
. Выполните шаги в.search(e)
. Если он уже существует, нам нужно только увеличить счет на 1. Однако, если он не существует, мы должны применить новую запись справа от подматрицыcounts
. Сначала мы увеличиваемnumberOfCountsStored
, чтобы отразить тот факт, что этот новый счет действителен:loc = numberOfCountsStored++
. Затем мы применим новую запись:counts[loc] = (e,⨯1)
. Наконец, мы добавим ссылку на нее в нашу таблицу рассылки, чтобы быстро найти ееlocationOfCounts[e] = loc
.
.delete(e)
. Выполните шаги в.search(e)
. Если он не существует, введите ошибку. Если счетчик = 2, все, что нам нужно сделать, это уменьшить счет на 1. В противном случае счет будет равен 1, а трюк здесь, чтобы обеспечить неизменность целогоnumberOfCountsStored
-counts[...]
(т.е. Все остается сохраненным слева частьcounts
) - выполнять свопы. Если удаление удалит последний элемент, мы потеряем паруcounts
, оставив отверстие в нашем массиве:[countPair0, countPair1, _hole_, countPair2, countPair{numberOfItemsStored-1}, ☠, ☠, ☠..., ☠]
. Мы заменяем это отверстие последним countPair, уменьшаемnumberOfCountsStored
, чтобы аннулировать отверстие, и обновляемlocationOfCounts[the_count_record_we_swapped.number]
, чтобы теперь он указывал на новое местоположение записи счетчика.