Оптимизируйте эту функцию на Python, которая считает количество пар с суммой ноль: def count_pairs(a): cnt = 0 for i in range(len(a)): for j in range(i+1, len(a)): if a[i] + a[j] == 0: cnt += 1 return cnt предложите оптимизацию с анализом временной и памяти сложности, обсуждением крайних случаев и подходов с использованием хеш‑таблиц, сортировки или многопоточности
Вот несколько быстрых и проверенных подходов с реализациями, сложностями и обсуждением краевых случаев.
1) Однопроходный хеш‑подсчёт (рекомендую — простая и быстрая) Код: def count_pairs(a): from collections import defaultdict cnt = 0 seen = defaultdict(int) for x in a: cnt += seen.get(-x, 0) seen[x] += 1 return cnt
Анализ:
Время: (\mathcal{O}(n)) в среднем (один проход).Память: (\mathcal{O}(k)), где (k) — число уникальных элементов ((k \le n)). Преимущества: один проход, корректно считает пары с нулями и дубликатами. Ограничения: деградация при плохих хешах теоретически, но в Python практически редко.
2) Подсчёт через Counter (симметричные пары) Код: def count_pairs_counter(a): from collections import Counter c = Counter(a) cnt = c.get(0, 0) (c.get(0, 0) - 1) // 2 # пары нулей: C(n0,2) for x in list(c): if x > 0 and -x in c: cnt += c[x] c[-x] return cnt
Анализ:
Время: построение Counter (\mathcal{O}(n)), затем перебор ключей (\mathcal{O}(k)) => всего (\mathcal{O}(n)) в среднем.Память: (\mathcal{O}(k)). Преимущество: явный контроль параллей, удобно если нужно знать частоты.
Формула для нулей: (\binom{n_0}{2} = \frac{n_0(n_0-1)}{2}).
3) Сортировка + два указателя (без доп. памяти, если сортировка in‑place) Код: def count_pairs_sorted(a): a = sorted(a) i, j = 0, len(a) - 1 cnt = 0 while i < j: s = a[i] + a[j] if s == 0: if a[i] == a[j]: k = j - i + 1 cnt += k (k - 1) // 2 break left_val = a[i]; right_val = a[j] left_count = right_count = 0 while i <= j and a[i] == left_val: left_count += 1; i += 1 while j >= i and a[j] == right_val: right_count += 1; j -= 1 cnt += left_count right_count elif s < 0: i += 1 else: j -= 1 return cnt
Анализ:
Время: (\mathcal{O}(n \log n)) (сортировка) + (\mathcal{O}(n)) для двух указателей.Память: (\mathcal{O}(1)) дополнительной (или (\mathcal{O}(n)), если сортировка требует). Преимущество: не нужно дополнительного хеша; полезно когда память критична и сортировка допустима.
4) Многопоточность/мультипроцессинг
В CPython многопоточность ограничена GIL — для CPU‑bound задач неэффективна. Используйте multiprocessing / ProcessPoolExecutor.Подход: разбить массив на чанки, в каждом чанке посчитать Counter (локально), затем объединить Counters в глобальный Counter и применить метод из (2). Временная сложность параллельно примерно (\mathcal{O}(n)) суммарно, но есть накладные расходы на сериализацию и объединение. Эффективно при очень больших данных и нескольких ядрах; требует больше памяти и IO.Альтернатива для потоков: map‑reduce стиль (счёт частот параллельно, затем редьюс).
Краевые случаи и замечания
Пустой список или один элемент: результат 0.Много одинаковых нулей: корректно считается формулой (\binom{n_0}{2}).Большие числа: в Python нет переполнения целых, но для float возможны проблемы с точностью (сравнение с нулём требует аккуратности).Если вход в виде строчек/нечисловых объектов — методы по‑прежнему работают при условии, что для элементов определены + и противоположный элемент (для хеша — хэшируемы).Для стриминговых данных: можно использовать однопроходный хеш‑подсчёт или внешнюю сортировку, если данные не помещаются в ОЗУ.
Рекомендация
Для общего случая — однопроходный хеш (первый вариант): прост, быстро и экономит время.Если память строго ограничена — используйте сортировку и два указателя.Для очень больших данных и многопроцессорных систем — разбить на чанки, строить Counters параллельно и объединять.
Если нужно, могу привести версию с multiprocessing для больших массивов или показать профилирование для вашего конкретного входа.
Вот несколько быстрых и проверенных подходов с реализациями, сложностями и обсуждением краевых случаев.
1) Однопроходный хеш‑подсчёт (рекомендую — простая и быстрая)
Код:
def count_pairs(a):
from collections import defaultdict
cnt = 0
seen = defaultdict(int)
for x in a:
cnt += seen.get(-x, 0)
seen[x] += 1
return cnt
Анализ:
Время: (\mathcal{O}(n)) в среднем (один проход).Память: (\mathcal{O}(k)), где (k) — число уникальных элементов ((k \le n)).Преимущества: один проход, корректно считает пары с нулями и дубликатами. Ограничения: деградация при плохих хешах теоретически, но в Python практически редко.
2) Подсчёт через Counter (симметричные пары)
Код:
def count_pairs_counter(a):
from collections import Counter
c = Counter(a)
cnt = c.get(0, 0) (c.get(0, 0) - 1) // 2 # пары нулей: C(n0,2)
for x in list(c):
if x > 0 and -x in c:
cnt += c[x] c[-x]
return cnt
Анализ:
Время: построение Counter (\mathcal{O}(n)), затем перебор ключей (\mathcal{O}(k)) => всего (\mathcal{O}(n)) в среднем.Память: (\mathcal{O}(k)).Преимущество: явный контроль параллей, удобно если нужно знать частоты.
Формула для нулей: (\binom{n_0}{2} = \frac{n_0(n_0-1)}{2}).
3) Сортировка + два указателя (без доп. памяти, если сортировка in‑place)
Код:
def count_pairs_sorted(a):
a = sorted(a)
i, j = 0, len(a) - 1
cnt = 0
while i < j:
s = a[i] + a[j]
if s == 0:
if a[i] == a[j]:
k = j - i + 1
cnt += k (k - 1) // 2
break
left_val = a[i]; right_val = a[j]
left_count = right_count = 0
while i <= j and a[i] == left_val:
left_count += 1; i += 1
while j >= i and a[j] == right_val:
right_count += 1; j -= 1
cnt += left_count right_count
elif s < 0:
i += 1
else:
j -= 1
return cnt
Анализ:
Время: (\mathcal{O}(n \log n)) (сортировка) + (\mathcal{O}(n)) для двух указателей.Память: (\mathcal{O}(1)) дополнительной (или (\mathcal{O}(n)), если сортировка требует).Преимущество: не нужно дополнительного хеша; полезно когда память критична и сортировка допустима.
4) Многопоточность/мультипроцессинг
В CPython многопоточность ограничена GIL — для CPU‑bound задач неэффективна. Используйте multiprocessing / ProcessPoolExecutor.Подход: разбить массив на чанки, в каждом чанке посчитать Counter (локально), затем объединить Counters в глобальный Counter и применить метод из (2). Временная сложность параллельно примерно (\mathcal{O}(n)) суммарно, но есть накладные расходы на сериализацию и объединение. Эффективно при очень больших данных и нескольких ядрах; требует больше памяти и IO.Альтернатива для потоков: map‑reduce стиль (счёт частот параллельно, затем редьюс).Краевые случаи и замечания
Пустой список или один элемент: результат 0.Много одинаковых нулей: корректно считается формулой (\binom{n_0}{2}).Большие числа: в Python нет переполнения целых, но для float возможны проблемы с точностью (сравнение с нулём требует аккуратности).Если вход в виде строчек/нечисловых объектов — методы по‑прежнему работают при условии, что для элементов определены + и противоположный элемент (для хеша — хэшируемы).Для стриминговых данных: можно использовать однопроходный хеш‑подсчёт или внешнюю сортировку, если данные не помещаются в ОЗУ.Рекомендация
Для общего случая — однопроходный хеш (первый вариант): прост, быстро и экономит время.Если память строго ограничена — используйте сортировку и два указателя.Для очень больших данных и многопроцессорных систем — разбить на чанки, строить Counters параллельно и объединять.Если нужно, могу привести версию с multiprocessing для больших массивов или показать профилирование для вашего конкретного входа.