Каков эффективный способ подсчета количества уникальных мультипликативных и аддитивных пар в списке целых чисел в Python?

Учитывая отсортированный массив A = [n, n + 1, n + 2, ... n + k] элементов, я пытаюсь подсчитать уникальное количество мультипликативных и аддитивных пар так, чтобы условие xy> = x + y было довольный. Где x и y - это индексы списка, а y> x.

Вот мой минимальный рабочий пример, использующий наивный метод грубой силы:

def minimum_working_example(A):
    A.sort()
    N = len(A)
    mpairs = []
    x = 0
    while x < N:
        for y in range(N):
            if x<y and (A[x]*A[y])>=(A[x]+A[y]):
                mpairs.append([A[x], A[y]])               
            else:
                continue    
        x+=1
    return len(mpairs)  

A = [1,2,3,4,5]
print(minimum_working_example(A))
#Output = 6, Unique pairs that satisfy xy >= x+y: (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)

Однако этот подход имеет экспоненциальную временную сложность для больших списков.

Какие существуют алгоритмы сортировки или поиска, которые позволят мне реализовать более эффективное решение?

Хотя вы реализуете это на языке программирования, это в основном кажется математическим вопросом (по крайней мере, для меня). Я бы рассмотрел 2 + 2 против 2 * 2, а затем подумал бы, что произойдет, если один вход меньше 2, и что произойдет, когда оба будут больше 2.

Jerry Coffin 17.12.2018 23:27

Используйте математику: x * y> = x + y, как только x или y больше 1, поэтому, если n> = 2, это k * (k + 1) / 2, а если n == 1, это k * (k- 1) / 2

Julien 17.12.2018 23:27

Разве x и y не были бы одними и теми же индексами в вашем примере ..?

Jab 17.12.2018 23:35

Алгоритм не экспоненциальный. Он квадратичный, и можно создавать списки, в которых результаты также масштабируются квадратично, следовательно, с точки зрения временной сложности мы мало что можем сделать.

Willem Van Onsem 17.12.2018 23:39

Спасибо @Julien. Я не обращал внимания.

Samuel Nde 17.12.2018 23:54

@Julien Где вы получаете k (k-1) / 2 и k (k + 1) / 2 для n == 1 и n> = 2? Я попытался ввести k = 1,2,3,4 в k (k-1) / 2 и k (k + 1) / 2, и получил 0,1,3,6 ... и 1,3,6 , 10 ...? Хотите узнать, что пытается сделать ваш подход? Вы упомянули, что это дает временную сложность O (1)

bwrr 18.12.2018 00:20

@bwrr см. мой ответ (обратите внимание, что в моем комментарии была небольшая ошибка, о которой никто не упоминал, несмотря на 5 голосов :)

Julien 18.12.2018 01:31
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
7
693
4

Ответы 4

Этот вопрос имеет математическое решение в закрытой форме, но если вы предпочитаете реализовать его на языке программирования, вам просто нужно найти все уникальные пары чисел из вашего списка и подсчитать число, удовлетворяющее вашим требованиям. itertools.combinations - ваш друг здесь:

import itertools

A = [1,2,3,4,5]
pairs = []
for x, y in itertools.combinations(A, 2):
    if x*y >= x + y:
        pairs.append((x,y))

Вывод

[(2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]

Прости. Я ушел.

Samuel Nde 17.12.2018 23:49

В каждой из этих пар произведение меньше суммы: 1 * 2 <1 + 2, 1 * 3 <1 + 3 и т. д.

Tim 17.12.2018 23:51

Этот подход кажется еще медленнее. Потребовалось 55 секунд, чтобы запустить список из 10000 случайных целых чисел.

bwrr 18.12.2018 00:16

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

xy >= x + y
xy - y >= x
y(x-1) >= x

Теперь, если все ваши элементы являются положительными целыми числами, вы получите

if x == 1, no solution
if x == 2, y >= 2
else x > 2
y >= x/(x-1)

В этом последнем случае x / (x-1) представляет собой дробь от 1 до 2; снова,

y >= 2

Решает неравенство.

Это дает вам тривиально доступное решение за время О (1); если вам нужны сами пары, вы ограничены печатью, которая составляет время О (п ^ 2).

Зачем останавливаться на достигнутом? В O (1) тоже есть "тривиальное" решение, о котором я упоминал в своем комментарии ...

Julien 17.12.2018 23:54

@Julien: Я не согласен: спецификации программы включают возврат списка подходящих пар. Это то, что побуждает ваше и мое решение перейти от О (1) к О (п ^ 2).

Prune 18.12.2018 00:30

Ну нет. OP запрашивает количество пар, а не сами пары. Код OP вычисляет список только для того, чтобы взять его длину в конце ... Так что нет необходимости в O (n ^ 2) (который, кстати, должен быть O (k ^ 2), учитывая их обозначения ...)

Julien 18.12.2018 01:14

Ах ... Я интерпретировал выходную строку как требующую как счетчика, так и самих пар. Я понимаю вашу точку зрения.

Prune 18.12.2018 17:56

Если вам нужно более математическое решение, учтите, что xy > x+y не имеет решений для y=1. В противном случае вы можете алгебраически обработать это до x > y/(y-1). Теперь, если у нас есть два последовательных целых числа положительный и разделив большее на меньшее, мы либо получим ровно 2 (если y = 2), либо получим некоторую дробь от 1 до 2, исключая. Обратите внимание, что x должен быть больше, чем это отношение y / (y-1), но также должно быть меньше y. Если y = 2, то единственное возможное значение x в нашем списке положительных целых чисел должно быть 1, и в этом случае совпадений нет, потому что 1 не больше 2/1. Таким образом, все это упрощается до «Для каждого числа y в нашем списке посчитайте все значения x, которые находятся в диапазоне [2, y)». Если вы сделаете математику, это должно привести к сложению 1 + 2 + 3 + ... + k, что является просто k(k+1)/2. Опять же, мы предполагаем, что n и k - положительные целые числа; вы можете вывести немного более сложную формулу, если учесть случаи для n <= 0.

Но если предположить, что вы ДЕЙСТВИТЕЛЬНО хотите придерживаться подхода грубой силы, а не прибегать к математическим рассуждениям, чтобы найти другой подход: я опробовал несколько вариантов, и вот более быстрое решение, основанное на следующем.

  • Вы сказали, что список уже отсортирован, поэтому я отказался от функции сортировки.
  • Точно так же "else: continue" не требуется, поэтому для простоты я отказался от него.
  • Вместо того, чтобы перебирать все значения x и y, а затем проверять, является ли x <y, вы можете просто заставить свой второй цикл проверять значения y в диапазоне от x + 1 до y. НО...
  • Вы можете использовать itertools для генерации уникальных пар всех чисел в вашем списке A
  • Если вы в конечном итоге действительно заботитесь только о длине списка pairs, а не о самих парах чисел, тогда вы можете просто подсчитывать пары по пути, а не сохранять их. В противном случае у вас может закончиться нехватка памяти при высоких значениях N.
  • Я получаю немного более быстрые результаты с эквивалентным тестом x (y-1) -y> 0. В большей степени, чем с x (y-1)> y.

Итак, вот что у меня есть:

def example4(A):
    mpair_count = 0
    for pair in itertools.combinations(A, 2):
        if pair[0]*(pair[1]-1) - pair[1] > 0:
            mpair_count += 1
    return mpair_count

Вот все рассчитано:

from timeit import default_timer as timer
import itertools

def minimum_working_example(A):
    A.sort()
    N = len(A)
    mpairs = []
    x = 0
    while x < N:
        for y in range(N):
            if x<y and (A[x]*A[y])>=(A[x]+A[y]):
                mpairs.append([A[x], A[y]])
            else:
                continue
        x+=1
    return len(mpairs)

# Cutting down the range
def example2(A):
    N = len(A)
    mpairs = []
    x = 0
    while x < N:
        for y in range(x+1,N):
            if (A[x]*A[y])>=(A[x]+A[y]):
                mpairs.append([A[x], A[y]])
        x += 1
    return len(mpairs)

# Using itertools
def example3(A):
    mpair_count = 0
    for pair in itertools.combinations(A, 2):
        if pair[0]*pair[1] > sum(pair):
            mpair_count += 1
    return mpair_count

# Using itertools and the different comparison
def example4(A):
    mpair_count = 0
    for pair in itertools.combinations(A, 2):
        if pair[0]*(pair[1]-1) - pair[1] > 0:
            mpair_count += 1
    return mpair_count

# Same as #4, but slightly different
def example5(A):
    mpair_count = 0
    for pair in itertools.combinations(A, 2):
        if pair[0]*(pair[1]-1) > pair[1]:
            mpair_count += 1
    return mpair_count

A = range(1,5000)
start = timer()
print(minimum_working_example(A))
end = timer()
print(end - start)

start = timer()
print(example2(A))
end = timer()
print(end - start)


start = timer()
print(example3(A))
end = timer()
print(end - start)

start = timer()
print(example4(A))
end = timer()
print(end - start)

start = timer()
print(example5(A))
end = timer()
print(end - start)

Результат:

12487503
8.29403018155
12487503
7.81883932384
12487503
3.39669140954
12487503
2.79594281764
12487503
2.92911447083

Итак, используя тот факт, что x*y >= x+y, если оба (ошибка в моем исходном комментарии), x и y - это >=2 (подробности см. В ответе @ Prune), тогда вы также можете удалить 0 и 1 из своего списка, если они появятся, потому что они не будут составить любую подходящую пару.

Итак, теперь, предполагая все числа или >=2 и у вас есть k из них (например, замените k на k-1 в следующей операции, если у вас есть n=1), все возможные пары будут удовлетворять вашему условию. А количество пар среди элементов k - это хорошо известная формула k*(k-1)/2 (погуглите, если не знаете). Время для вычисления этого числа по существу одинаково (одно умножение, одно деление) независимо от того, какое у вас значение k (если только вы не начнете переходить к сумасшедшим большим числам), поэтому сложность равна O (1).

Это предполагает, что ваши целые числа положительны, в противном случае формула будет немного сложнее, но все же возможно в качестве решения в закрытой форме.

Другие вопросы по теме