Какова временная и пространственная сложность задачи 3Sum со следующим алгоритмом?

Была эта проблема, которая требовала вернуть все уникальные триплеты элементов массива, сумма которых равна нулю (перестановка мест двух элементов в триплете не считается уникальной).

Я придумал следующий код:

function threeSum(nums) {
  nums.sort((a, b) => a - b);
  const result = [];
  for (let i = 0; i < nums.length; i++) {
    // skipping duplicates
    if (i !== 0 && nums[i] === nums[i - 1]) continue;
    let left = i + 1;
    let right = nums.length - 1;
    while (left < right) {
      const s = nums[i] + nums[left] + nums[right];
      // too small; move to the right
      if (s < 0) left++;
      // too big; move to the left
      else if (s > 0) right--;
      // bingo
      else {
        result.push([nums[i], nums[left], nums[right]]);
        //skipping duplicates
        while (left + 1 < right && nums[left] === nums[left + 1]) left++;
        while (right - 1 > left && nums[right] === nums[right - 1]) right--;
        left++;
        right--;
      }
    }
  }
  return result;
};
// expected output: [[-4,-2,6],[-4,0,4],[-4,1,3],[-4,2,2],[-2,-2,4],[-2,0,2]]
console.log(threeSum([-4,-2,-2,-2,0,1,2,2,2,3,3,4,4,6,6]))

Я думаю, что временная сложность составляет O(n^2). В начале есть сортировка, которая, как мы предполагаем, составляет O(n log n), а вложенный цикл работает примерно (n^2)/2 раз, что преобразуется в О(n^2). Таким образом, в конце у нас остается O(n log n + n^2), и, поскольку n log n имеет меньшую степень, он удаляется, оставляя нас с O(n^2).

Я не совсем уверен в пространственной сложности, но интуитивно думаю, что это O(n).

Не могли бы вы исправить/подтвердить мои рассуждения/догадки о пространственно-временной сложности?


person Alireza    schedule 24.08.2019    source источник


Ответы (3)


Это выглядит как стандартный подход к решению задачи 3SUM за квадратичное время. Однако я не согласен с другими ответами, касающимися пространственной сложности, и считаю, что она квадратична, поскольку может быть квадратично много различных троек, суммирующихся с 0.


Рассмотрим следующий пример:

[1, -1, 2, -2, ..., n-1, 1-n, n, -n], где n четное.

В этом конкретном случае имеется n²/4 - n/2 различных троек, сумма которых равна 0 (см. вывод этого результата ниже). Это квадратично много по размеру массива (массив имеет длину 2*n элемента). Поскольку вы храните все эти решения, это означает, что вам нужен квадратичный объем памяти, а линейное O(n) не сократит его.

Таким образом, пространственная сложность наихудшего случая также квадратична (легко показать, что не может быть больше, чем квадратично много различных троек, сумма которых равна 0).


Вывод результата:

Для любого положительного числа a в этой последовательности мы можем выбрать b = k и c = -(a+k), чтобы получить тройку, где a — наименьший элемент по абсолютной величине, при условии, что k > a и a+k <= n т. е. a < k <= n-a. Это дает нам n-2*a вариантов для k (мы никогда не считаем дважды, так как b всегда положительное, а c всегда отрицательное).

Суммируя все возможные a, мы получаем в общей сложности: sum((n-2*a) for a in 1...n/2) = n²/4 - n/2 = Ω(n²).

person Tassle    schedule 24.08.2019
comment
спасибо @tassle, это поучительно. Я вижу, куда вы направляетесь. Только что сделал небольшой тест, где длина результата была более чем в два раза больше длины ввода. Но ваш ответ немного загадочен :) можете ли вы привести конкретный пример? Спасибо за помощь. - person Alireza; 24.08.2019
comment
Суть в том, что для конкретного примера, который я рассматриваю (то есть [1,-1,2,-2,...,n,-n] для любого четного n, которое вы хотите), существует n²/4 - n/2 различных троек, суммирующих до 0. Это квадратично много по размеру массива (массив имеет длину 2 * n элементов). Поскольку вы храните все эти решения, это означает, что вам нужен квадратичный объем памяти, а линейный O(n) его не урежет. - person Tassle; 24.08.2019
comment
Что касается конкретности, я не знаю, как я могу получить более конкретную информацию, чем приведенный мной пример :/ - person Tassle; 24.08.2019
comment
нет, это нормально :) спасибо. Можете добавить пример к своему ответу для некоторых будущих призраков, которые потерялись. - person Alireza; 24.08.2019
comment
Так лучше? :) - person Tassle; 24.08.2019

Я согласен с вашими мыслями о временной сложности. У вас есть цикл в цикле, и вы всегда перемещаете указатель во внутреннем цикле как минимум на 1 (left или right). Так что, как вы сказали, O (n ^ 2/2) или O (n ^ 2)

РЕДАКТИРОВАТЬ: для космической сложности я согласен с ответом Тассле

person Dimitri L.    schedule 24.08.2019
comment
Ах, мило. А временная сложность? - person Alireza; 24.08.2019
comment
Обновил мой ответ. Я полностью согласен с вашими мыслями о временной сложности, поэтому я не добавил ее в свой первоначальный ответ. - person Dimitri L.; 24.08.2019
comment
Я не согласен с тем, что в целом существует только линейное число троек. Их может быть квадратично много (см. мой ответ). - person Tassle; 24.08.2019

Да, временная сложность равна O(n^2), где n = nums.length, и вашего объяснения достаточно.

Использование пространственной сложности O(n) также является правильным из-за алгоритма сортировки слиянием, используемого в методе sort(), который имеет эту пространственную сложность. Сложность пространства относится к дополнительному пространству элементов/переменных, которое вы пишете в своем коде, помимо переменных, специфичных для данной проблемы. Для кода, кроме метода sort(), есть две переменные 'left' и 'right' вместе с результатом массива, поэтому этот цикл имеет пространственную сложность O (n + 2), а затем соответствует им во внутреннем цикле while. также имеет переменную 's', то обратите внимание, что на каждой итерации есть 2 возможности: -

  1. это тот же контейнер (т. е. место в памяти), содержимое которого (т. е. значение переменной) изменяется, и, таким образом, в целом используется только 3 контейнера, что заключает сложность пространства как O (константа) или просто O (1)
  2. Каждый раз, когда другой контейнер выделяется переменной для его содержимого; однако это всегда будет сопровождаться освобождением предыдущего контейнера. Таким образом, в целом у нас есть только 3 дополнительных контейнера одновременно, что снова приведет к пространственной сложности как O (1).

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

Надеюсь, поможет!

person Sharad Nanda    schedule 24.08.2019
comment
Это полезно, спасибо. Но у нас также есть массив result, в котором мы отслеживаем все тройки решений. Это дополнительное место. Вопрос в следующем: какова верхняя граница/максимальное количество возможных решений, которые потенциально могут быть добавлены в массив result? - person Alireza; 24.08.2019
comment
Искренние извинения, я пропустил это. Я исправил свой ответ. - person Sharad Nanda; 24.08.2019