С# - сложность времени для генерации всех пар в массиве

Учитывая массив чисел, сгенерируйте все уникальные пары.

Например, учитывая [ 1, 2, 3, 4, 5 ] единственная пара чисел будет:

(1, 2), (1, 3), (1, 4), (1, 5)

(2, 3), (2, 4), (2, 5)

(3, 4), (3, 5)

(4, 5)

Мое решение таково:

int[] numbers = new int[] { 1, 2, 3, 4, 5 };
HashSet<Pair> pairs = new HashSet<Pair>();

for(int i = 0; i < numbers.Length; i++)
{
    for(int j = i + 1, j < numbers.Length; j++)
    {
        pairs.Add(new Pair(numbers[i], numbers[j]));
    }
}

Я думаю, что временная сложность для этого выглядит как O (n 2 - 1), вычитая 1, потому что итерация j всегда 1 короче i

Проведя немного исследований в этой проблеме, я не могу найти окончательных ответов о том, можно ли это сделать быстрее. Есть ли лучшие решения, чем O (n 2 - 1)?

Ответ 1

Один из способов подумать о "есть ли более быстрый способ решения проблемы" - это посмотреть на размер вывода для определенного формата (который вы считаете "вероятно самым большим/самым трудным для решения").

Если выход O(n^2), то вы не можете решить проблему быстрее, чем в O(n^2), потому что вам нужно потратить не менее O(1) для каждого вывода.

Вы можете увидеть шаблон там, если у вас есть 5 номеров в формате [1, 2, 3, 4, 5], уникальные пары принимают

4 pairs in first row
3 pairs in second row
2 pairs...
1 pair

потому что они выглядят как

(1, 2), (1, 3), (1, 4), (1, 5)

(2, 3), (2, 4), (2, 5)

(3, 4), (3, 5)

(4, 5)

Если у вас есть 20 переменных в массиве (в формате [1, 2, 3,... 18, 19, 20]), это будет следующим:

19 pairs
18 pairs
...
2 pairs
1 pair

Поэтому выходной размер равен (n-1) + (n-2) + (n-3)... + 3 + 2 + 1. Вы должны это суммировать (посмотрите, как суммировать серию), а результат - O(n^2)

Что было доказано?

В худшем случае сценарий AT LEAST O(n^2).

Также обратите внимание, что в настоящий момент мы не знаем реальной сложности худшего случая: alghorithm может быть еще медленнее (мы просто обнаруживаем, что некоторый вход принимает O(n^2)). Мы точно знаем, что по крайней мере эти данные принимают O(n^2). Это может быть быстрее или медленнее для разных входных данных.


Включение: у нас есть доказательство того, что алгоритм занимает не менее O(n^2) времени (в худшем случае), вы создали алгоритм, который работает максимум O(n^2) (как описано в spyc post) = У вас есть оптимальный алгоритм.


Дополнительная информация для решения OP: Обнаружение столкновений с HashSet - это только "псевдоконстант" и только для небольших чисел и "удачи". Для большого количества чисел требуется O(n). Таким образом, вы можете получить выход n^2 и каждый из них будет обрабатывать n до процесса, что приведет к сложности n^3.

Вы можете решить эту проблему с предварительной обработки задачи:

1) Сортировка - требуется только n log n, поэтому не влияет на n^2 любом случае

2) Удалите числа, повторяющиеся более двух раз [1, 3, 3, 3, 5] → [1, 3, 3, 5], это O(n)

3) Затем используйте свой алгоритм с этим обновлением:

3.1) В начале цикла for i: if (number[i] == number[i-1]) continue;

3.2) В начале цикла for j: помните последнюю пару. При добавлении новой пары, посмотрите на последнюю пару и проверьте, одинаково она или нет. Если так - continue;

Пример:

Input: [1, 3, 3, 5]

1)i=0, j=1, number[0]=1, number[1]=3 -> add (1, 3)
2)i=0, j=2, number[0]=1, number[2]=3 -> same as last pair, use continue
3)i=0, j=3, number[0]=1, number[3]=5 -> add (1, 5)
4)i=1, j=2, number[1]=3, number[2]=3 -> add (3, 3)
5)i=1, j=3, number[1]=3, number[3]=5 -> add (3, 5)
6)i=2, before go to j-cycle, check number[i] === number[i-1] It is true, use continue

Ответ 2

Это выглядит следующим образом:

first for loop - O(n)
    second for loop - O(n-1)

Оптимальная сложность времени:

enter image description here

  • Несмотря на то, что эта итерация ничтожно мала, и вы должны рассчитать сложность времени для наихудшего сценария, который является enter image description here

Вы также можете использовать биномиальный коэффициент для перестановок, чтобы получить число перестановок определенной строки. Например:

enter image description here

Если у вас есть 6 цифр {0,1,2,3,4,5} (n = 6), и вы хотите знать, сколько различных перестановок вы можете сделать, то есть: (3,5), (5,3) и т.д.... тогда (k = 2, две цифры в каждой группе), количество перестановок будет:

enter image description here разные перестановки, обратите внимание, что в этом случае (3,5), (5,3) учитываются индивидуально, поэтому порядок этого имеет значение. Если вы хотите (5,3) и (3,5) считаться одной комбинацией, то уравнение имеет следующий вид:

enter image description here


Пример реализации, вычисление перестановок!

static long factorial(long x) // calcs the factorial TimeCmplx = O(n)
{
    if (x == 1)
        return x;
    return x * factorial(x - 1);
}

static long permutations(long n , long k) //Check that (n , k) >= 0
{            
    // Permutations , n!/(n-k)!
    return factorial(n) / factorial(n - k);
}

Ответ 3

Я думаю, что временная сложность для этого выглядит как O (n2 - 1), вычитающая 1, потому что итератор j всегда 1 короче i

Если бы это имело значение (большая-O-нотация, как правило, вы только пишете термин с самым быстрым ростом), то у вас есть итерации я над [0, n), каждая из которых содержит итерацию j над [i + 1, n), так что число итераций (n ∙ (n -1))/2 не n² -1.

Кроме того, изменение в HashSet вместо изменения списка приведет к наихудшему исполнению, но не к амортизированному значению - если Pair.GetHashCode() всегда возвращал одно и то же значение, вы бы натолкнули его на O (n³), как в случаи, когда коллизии являются общими установками хэш-набора, становятся O (n), а не постоянными.

Ответ 4

Это область треугольного алгоритма.

У вас есть N входов, и вам нужен треугольник выходов.

Ваш выходной треугольник имеет высоту N-1 и ширину N-1.

Area of a triangle = height * width / 2
                   = (N-1)  * (N-1) / 2
                   = (N^2 - 2N + 1) / 2

O(n^2 - n) всегда будет минимальной/оптимальной стоимостью алгоритма!