Вычислите и обоснуйте временную и пространственную сложность следующего фрагмента на Python, укажите возможные оптимизации и случаи худшего/среднего/лучшего поведения: def f(a): res=[]; for i in range(len(a)): for j in range(i+1,len(a)): if a[i]+a[j]==0: res.append((i,j)); return res
def f(a): res = [] for i in range(len(a)): for j in range(i+1, len(a)): if a[i] + a[j] == 0: res.append((i, j)) return res
1) Временная сложность
Внешний цикл пробегает i = 0..n-1, внутренний для каждого i пробегает примерно n-i-1 итераций.Общее число проверок if равно сумме (n-1) + (n-2) + ... + 1 = n(n-1)/2 = Θ(n^2).Каждая итерация делает O(1) работу (сложение, сравнение, иногда append). Итого: время Θ(n^2) (точнее — O(n^2) и Ω(n^2) для растущего n). Для очень маленьких n (0 или 1) затраты константны, но асимптотически алгоритм квадратичный.
2) Пространственная сложность
Дополнительная память кроме входа: список res и константные переменные.В худшем случае количество найденных пар может быть O(n^2) (например, все элементы 0 → все пары подходят, их n(n-1)/2).Поэтому вспомогательная память — O(n^2) в худшем случае (если учитывать память под результат). Если не считать память под результат, дополнительные вспомогательные O(1). Итого: O(n^2) (включая результат) или O(1) (без учёта результата).
3) Лучшее/среднее/худшее поведение
В данном коде нет раннего выхода, поэтому для произвольного n (при n большие) число итераций фиксировано ≈ n(n-1)/2, следовательно: Лучшее/среднее/худшее асимптотически — все Θ(n^2).Исключение: для n = 0 или 1 затраты O(1), но это тривиальные частные случаи.
4) Возможные оптимизации и альтернативы Задача: найти все парные индексы (i,j), такие что a[i] + a[j] == 0.
a) Хеш-таблица (dict value → список индексов)
Построить словарь d: значение → список индексов, за O(n) времени и O(n) памяти.Для каждого значения v и списка индексов Lv, если есть список Lm для m = -v, нужно сгенерировать все пары между Lv и Lm (аккуратно, чтобы не дублировать: обрабатывать только v > -v или обрабатывать отдельно v == 0).Время: O(n + R), где R — число возвращаемых пар (так как нужно перечислить все результатные пары). Память: O(n + R).Это оптимально по сложности в том смысле, что вы не можете сделать лучше по времени, чем O(n + R), потому что нужно прочитать вход (O(n)) и вывести R пар.
Краткая схема (псевдокод):
d = {}для i, x in enumerate(a): d[x].append(i)для каждого x в ключах: if x > 0 and -x in d: сформировать декартово произведение d[x] × d[-x] if x == 0: сформировать все сочетания индексов в d[0] по 2
b) Сортировка + два указателя
Создать список пар (value, index), отсортировать по value: O(n log n) время, O(n) память.Два указателя слева/справа для поиска сумм равных 0; при равных значениях нужно корректно обрабатывать дубликаты (может потребоваться генерация декартовых произведений между блоками одинаковых значений и соответствующими блоками противоположных значений).Время: O(n log n + R), память: O(n + R).Подходит, если сортировка допустима; для очень большого R (почти n^2) оба метода всё равно затратны из-за размера вывода.
c) Если требуется только узнать, существует ли хоть одна такая пара
Можно использовать hash set: пройти по элементам, для каждого x проверить, есть ли -x уже в set; если да — вернуть True. Ожидаемое время O(n) и память O(n). (Но это не возвращает все пары.)
d) Мелкие оптимизации в Python
Локальные переменные (a локально, переприсваивание a_i = a[i]) могут чуть ускорить, но не меняют асимптику.Использовать itertools.combinations может быть компактнее, но быстрее не станет asymptotically.
5) Вывод/рекомендации
Исходный код: время Θ(n^2), память O(n^2) в худшем случае (учитывая результат).Если нужно вернуть все пары — оптимальным по времени является алгоритм с хеш-таблицей или сортировкой: время O(n + R) (хеш) или O(n log n + R) (сортировка), где R — размер результата. Часто хеш-метод предпочтительнее: O(n + R) время, O(n + R) память.Если нужен только факт существования пары — можно получить O(n) время и O(n) память с hash-set и ранним выходом.
Если хотите, могу привести конкретную реализацию хеш-версии, которая возвращает все пары индексов без дублирования.
Разбор фрагмента:
def f(a):
res = []
for i in range(len(a)):
for j in range(i+1, len(a)):
if a[i] + a[j] == 0:
res.append((i, j))
return res
1) Временная сложность
Внешний цикл пробегает i = 0..n-1, внутренний для каждого i пробегает примерно n-i-1 итераций.Общее число проверок if равно сумме (n-1) + (n-2) + ... + 1 = n(n-1)/2 = Θ(n^2).Каждая итерация делает O(1) работу (сложение, сравнение, иногда append).Итого: время Θ(n^2) (точнее — O(n^2) и Ω(n^2) для растущего n). Для очень маленьких n (0 или 1) затраты константны, но асимптотически алгоритм квадратичный.
2) Пространственная сложность
Дополнительная память кроме входа: список res и константные переменные.В худшем случае количество найденных пар может быть O(n^2) (например, все элементы 0 → все пары подходят, их n(n-1)/2).Поэтому вспомогательная память — O(n^2) в худшем случае (если учитывать память под результат). Если не считать память под результат, дополнительные вспомогательные O(1).Итого: O(n^2) (включая результат) или O(1) (без учёта результата).
3) Лучшее/среднее/худшее поведение
В данном коде нет раннего выхода, поэтому для произвольного n (при n большие) число итераций фиксировано ≈ n(n-1)/2, следовательно:Лучшее/среднее/худшее асимптотически — все Θ(n^2).Исключение: для n = 0 или 1 затраты O(1), но это тривиальные частные случаи.
4) Возможные оптимизации и альтернативы
Задача: найти все парные индексы (i,j), такие что a[i] + a[j] == 0.
a) Хеш-таблица (dict value → список индексов)
Построить словарь d: значение → список индексов, за O(n) времени и O(n) памяти.Для каждого значения v и списка индексов Lv, если есть список Lm для m = -v, нужно сгенерировать все пары между Lv и Lm (аккуратно, чтобы не дублировать: обрабатывать только v > -v или обрабатывать отдельно v == 0).Время: O(n + R), где R — число возвращаемых пар (так как нужно перечислить все результатные пары). Память: O(n + R).Это оптимально по сложности в том смысле, что вы не можете сделать лучше по времени, чем O(n + R), потому что нужно прочитать вход (O(n)) и вывести R пар.Краткая схема (псевдокод):
d = {}для i, x in enumerate(a): d[x].append(i)для каждого x в ключах:if x > 0 and -x in d: сформировать декартово произведение d[x] × d[-x]
if x == 0: сформировать все сочетания индексов в d[0] по 2
b) Сортировка + два указателя
Создать список пар (value, index), отсортировать по value: O(n log n) время, O(n) память.Два указателя слева/справа для поиска сумм равных 0; при равных значениях нужно корректно обрабатывать дубликаты (может потребоваться генерация декартовых произведений между блоками одинаковых значений и соответствующими блоками противоположных значений).Время: O(n log n + R), память: O(n + R).Подходит, если сортировка допустима; для очень большого R (почти n^2) оба метода всё равно затратны из-за размера вывода.c) Если требуется только узнать, существует ли хоть одна такая пара
Можно использовать hash set: пройти по элементам, для каждого x проверить, есть ли -x уже в set; если да — вернуть True. Ожидаемое время O(n) и память O(n). (Но это не возвращает все пары.)d) Мелкие оптимизации в Python
Локальные переменные (a локально, переприсваивание a_i = a[i]) могут чуть ускорить, но не меняют асимптику.Использовать itertools.combinations может быть компактнее, но быстрее не станет asymptotically.5) Вывод/рекомендации
Исходный код: время Θ(n^2), память O(n^2) в худшем случае (учитывая результат).Если нужно вернуть все пары — оптимальным по времени является алгоритм с хеш-таблицей или сортировкой: время O(n + R) (хеш) или O(n log n + R) (сортировка), где R — размер результата. Часто хеш-метод предпочтительнее: O(n + R) время, O(n + R) память.Если нужен только факт существования пары — можно получить O(n) время и O(n) память с hash-set и ранним выходом.Если хотите, могу привести конкретную реализацию хеш-версии, которая возвращает все пары индексов без дублирования.