← Назад

705.Design HashSet

Ссылка на задачу: https://leetcode.com/problems/design-hashset/description/


массивы

easy

хеш-таблицы


Описание

Спроектируйте хеш-сет без использования каки-либо встроенных библиотек, в которых уже есть реализация хеш-таблиц и прочего.

Имплементация класса MyHashSet:


Примеры

Пример 1

Input:

“MyHashSet""add""add""contains""contains""add""contains""remove""contains”
[][1][2][1][3][2][2][2][2]

Output:

nullnullnulltruefalsenulltruenullfalse

Объяснение:


Ограничения


Решение

Вспомним определение структуры данных хеш-сет. Данная структура данных хранит только уникальные элементы, причем доступ до элемента, его добавление и удаление - операции, сложность которых О(1), то есть не зависит от размера структуры. Строгость порядка элементов отсутствует, то есть он не гарантирован.

Задача сводится к тому, чтобы придумать, как спроектировать класс MyHashSet, удовлетворяющий всем этим условиям. Интуитивно понятно, что нужно использовать какой-то фундаментальный контейнер “под капотом”. Но какой?

Будем идти по порядку условий:

Как будто бы массив почти подходит. Два фактора мешают его использовать: необходимость иметь достаточную емкость для добавления элемента за О(1) и сдвиг элементов при удалении. Нужно как-то нивелировать эти недостатки.

Первую проблему можно решить, обратившись к ограничениям задачи. Сказано, что key лежит в диапазоне от 0 до 10^6 включительно. Так, если мы заранее выделим память под 1000001 целочисленных элементов, то нам всегда хватит емкости с 100%-ной гарантией. Действительно, если key принять за индекс массва, то максимальный индекс - это 10^6, значит размер массива должен быть 1000001, чтобы никогда не выйти за его границы.

В таком подходе вторая проблема тоже решается. Выделив память заранее под самый максимально длинный массив, мы откинули в строноу проблему сдвига элементов при удалении. Вместо сдвига элементов при удалении мы будем приравнивать удаляемому элементу с индексом key некоторое специальное значение. Таким значением может являться False. False - элемента нет, True - элемент есть.

Алгоритм:

  1. Инициализировать массив булевых значений размером 1000001

  2. Присвоить всем элементам значение False (пустой хеш-сет)

  3. В случае проверки наличия элемента key нужно проверить, равен ли элемент с индексом key значению True

  4. При добавлении элемента key достаточно присвоить True элементу массива с индексом key

  5. При удалении элемента key достаточно присвоить False элементу массива с индексом key

Код:

python

class MyHashSet:
    def __init__(self):
        self.values = [False] * (10**6 + 1)

    def add(self, key: int) -> None:
        self.values[key] = True

    def remove(self, key: int) -> None:
        self.values[key] = False

    def contains(self, key: int) -> bool:
        return self.values[key]

C++

class MyHashSet {
public:
    MyHashSet() {
        for (int i = 0; i < 1000001; ++i) {
            arr_[i] = false;
        }
    }
    
    void add(int key) {
        arr_[key] = true;
    }
    
    void remove(int key) {
        arr_[key] = false;
    }
    
    bool contains(int key) {
        return arr_[key];
    }
private:
    bool arr_[1000001];
};

Примечание: по идее bool в C++ должен занимать 1 бит (0 или 1), но он занимает 1 байт, так как минимальное адресуемое пространство памяти - 1 байт. Так, в массиве bool arr[1000001] все равно каждый элемент занимает 1 байт. Однако, хорошо зная C++, можно применить трюк с использованием std::vector<bool>, так как std::vector<bool> - это специализированный шаблон, а не обычный контейнер std::vector.

Сложность

Наш хеш-сет удовлетворяет всем требованиям: