Big O

Big O — это способ описать, как растёт стоимость алгоритма при увеличении
размера входных данных.

Чаще всего говорят о времени:

O(1)
O(log n)
O(n)
O(n log n)
O(n^2)

Но важно: O(...) само по себе не означает «худший случай».

O(...) описывает рост функции. А вот какую именно функцию мы анализируем,
нужно уточнять:

worst-case time
average-case time
best-case time
expected time
amortized time
space complexity

То есть можно сказать:

worst-case O(n)
average-case O(1)
expected O(1)
amortized O(1)
space O(n)

В алгоритмических задачах, особенно на собеседованиях, когда просто говорят:

this algorithm is O(n)

обычно по умолчанию имеют в виду worst-case time complexity.

Но это соглашение контекста, а не свойство Big O как математической записи.

Правильнее мыслить так:

Big O — это верхняя оценка роста для выбранного режима анализа.

Можно анализировать разные режимы:

worst-case O(n^2)
average-case O(n log n)
best-case O(n)
amortized O(1)
expected O(1)

Где возникает путаница

Классический пример — hash map и hash set.

Когда говорят:

HashMap lookup is O(1)

это почти всегда сокращённая фраза. Полная версия:

HashMap lookup is O(1) on average / expected case

А в худшем случае lookup в hash map может быть:

O(n)

Потому что все ключи теоретически могут попасть в одну и ту же корзину из-за
hash collision.

Пример с hash buckets

Допустим, hash map внутри имеет корзины:

bucket 0
bucket 1
bucket 2
bucket 3

Нормальная ситуация:

bucket 0: [apple]
bucket 1: [cat]
bucket 2: [dog]
bucket 3: [sun]

Тогда lookup быстрый:

hash("dog") -> bucket 2 -> нашли

Это выглядит как O(1).

Плохая ситуация:

bucket 0: [apple, cat, dog, sun, table, car, ...]
bucket 1: []
bucket 2: []
bucket 3: []

Теперь hash привёл всех в одну корзину, и внутри этой корзины нужно искать среди
многих элементов.

Тогда lookup превращается примерно в:

O(n)

HashMap lookup

Для обычной hash map или hash set точная формулировка такая:

lookup: average / expected O(1)
lookup: worst-case O(n)

Для Java HashMap есть дополнительная защита: когда в одной корзине становится
слишком много элементов, Java может превратить цепочку в дерево. Тогда худший
случай для этой корзины становится ближе к:

O(log n)

Но в алгоритмических задачах обычно всё равно говорят проще:

HashMap / HashSet lookup is O(1) average case

Почему на LeetCode всё равно пишут O(1)

Потому что для большинства задач предполагается нормальная hash function и
нормальное распределение ключей.

Например:

seen = set()

for x in nums:
    if x in seen:
        return True

    seen.add(x)

Технически:

x in seen -> average O(1), worst-case O(n)

Но весь алгоритм обычно записывают как:

Time:  O(n)
Space: O(n)

Потому что берут ожидаемую нормальную работу hash set.

Amortized complexity

Amortized complexity описывает среднюю стоимость операции в длинной серии
операций, даже если отдельная операция иногда дорогая.

Пример — динамический массив.

Обычно push в конец работает за:

O(1)

Но иногда массиву нужно выделить новый блок памяти и скопировать старые
элементы. Такая отдельная операция может стоить:

O(n)

При этом за длинную последовательность добавлений средняя стоимость одного
push остаётся:

amortized O(1)

Space complexity

Big O используют не только для времени, но и для памяти.

Например:

seen = set(nums)

По времени это обычно:

Time: O(n)

По памяти:

Space: O(n)

Потому что мы создаём дополнительную структуру, которая в худшем случае хранит
все элементы.

Главное различение

Big O — это не обязательно худший случай.

Big O — это верхняя оценка роста для выбранного режима анализа.

Поэтому правильно говорить так:

HashMap lookup не "абсолютно O(1)".
HashMap lookup — O(1) в среднем / ожидаемо.
В худшем случае может быть хуже.

В собеседовательном контексте, если ничего не уточняют, часто подразумевают
worst case. Но с hash-структурами есть устоявшееся сокращение: говорят O(1),
подразумевая average / expected case.

Прокрутить вверх