Как оценивать сложность алгоритма
Сначала определить n, затем посчитать, сколько раз выполняется доминирующая операция.
Шпаргалка
array[i] // O(1)
list.get(i) // ArrayList // O(1)
list.get(i) // LinkedList // O(n)
map.get(key) // HashMap average // O(1), не абсолютная гарантия
treeMap.get(key) // O(log n)
Arrays.sort(int[]) // O(n log n) worst для актуальной JDK-реализацииЦиклы
for (int i = 0; i < n; i++) work(); // Θ(n)
for (int i = 1; i < n; i *= 2) work(); // Θ(log n)
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) work(); // Θ(nm)Не считать только строки кода. list.contains(x) внутри цикла по n элементам делает решение O(n²), хотя визуально это один вызов.
Рекурсия
Записать recurrence:
- binary search:
T(n) = T(n/2) + O(1) = O(log n); - merge sort:
T(n) = 2T(n/2) + O(n) = O(n log n); - наивный Fibonacci: две перекрывающиеся ветви → экспоненциальный рост.
Практический порядок проверки
- Время worst case.
- Дополнительная память.
- Глубина рекурсии и риск stack overflow.
- Стоимость подготовки: сортировка ради одного binary search может быть дороже linear scan.
- Число обращений к сети/БД — часто важнее локальной Big O.
См. Big O, Оценка асимптотической сложности алгоритмов.
На собеседовании
- Два последовательных цикла по n?
O(n + n) = O(n). - Вложенные циклы n и m?
O(nm). - Почему вызов метода нельзя считать O(1)? Нужно раскрыть его реализацию.
- Когда сортировать ради поиска невыгодно? При одном запросе:
O(n log n)подготовки противO(n)scan.