Почему средняя временная сложность операций get() и put() в HashMap оценивается как O(1)?

«Почему средняя временная сложность операций get() и put() в HashMap оценивается как O(1)?» — вопрос из категории Алгоритмы и структуры данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Средняя сложность O(1) (амортизированная константная) достигается благодаря хеш-функции, которая равномерно распределяет ключи по «корзинам» (buckets), и структуре данных внутри корзины.

Как работает HashMap (упрощенно):

  1. Вычисление индекса: По хеш-коду ключа вычисляется индекс корзины: index = hash(key) & (n-1).
  2. Прямой доступ: По индексу происходит мгновенный доступ к нужной корзине.
  3. Разрешение коллизий: Если в одной корзине оказывается несколько элементов (коллизия), в Java 8+ они хранятся как сбалансированное дерево (TreeNodes) при большом количестве, что дает O(log n) внутри корзины, или как список O(n). При хорошей хеш-функции и правильном размере таблицы корзины остаются небольшими.

Пример, иллюстрирующий принцип:

HashMap<String, Integer> map = new HashMap<>();
map.put("key1", 100); // ~O(1): вычисляем хеш, находим корзину, кладем значение.
Integer val = map.get("key1"); // ~O(1): вычисляем тот же хеш, сразу идем в ту же корзину.

Условия для O(1):

  • Качественная хеш-функцияString, Integer она уже реализована), дающая равномерное распределение.
  • Отсутствие перегрузки: Коэффициент загрузки (load factor, по умолчанию 0.75) контролирует своевременное увеличение количества корзин (rehashing), чтобы цепочки в них оставались короткими.

Важно: В худшем случае (все ключи попали в одну корзину из-за плохой хеш-функции) сложность деградирует до O(n) или O(log n). Поэтому O(1) — это средняя, а не гарантированная сложность.