Есть ли эффективный алгоритм, кроме перебора, чтобы найти три целых числа?
Ага; мы можем решить это за O (n 2 ) раз! Во-первых, учтите, что ваша проблема P
может быть эквивалентно сформулирована несколько иначе, что устраняет необходимость в «целевом значении»:
Исходная задача P
: Дано массив A
из n
целых чисел и заданного значения S
, существует ли 3-кортеж от того, A
что суммы в S
?
модифицированная проблема P'
: существует ли массив A
из 3 n
целых чисел от A
этой суммы до нуля?
Обратите внимание , что вы можете перейти от этой версии проблемы P'
с P
вычитанием вашего S / 3 от каждого элемента A
, но теперь вам не нужно целевое значение больше.
Ясно, что если бы мы просто протестировали все возможные 3-кортежи, мы бы решили проблему в O (n 3 ) - это базовая линия грубой силы. Можно ли сделать лучше? Что если мы выберем кортежи несколько умнее?
Во-первых, мы потратим некоторое время на сортировку массива, что обойдется нам в первоначальный штраф O (n log n). Теперь мы выполняем этот алгоритм:
for (i in 1..n-2) {
j = i+1 // Start right after i.
k = n // Start at the end of the array.
while (k >= j) {
// We got a match! All done.
if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])
// We didn't match. Let's try to get a little closer:
// If the sum was too big, decrement k.
// If the sum was too small, increment j.
(A[i] + A[j] + A[k] > 0) ? k-- : j++
}
// When the while-loop finishes, j and k have passed each other and there's
// no more useful combinations that we can try with this i.
}
Этот алгоритм работает путем размещения трех указателей, i
, j
, и k
в различных точках массива. i
начинается в начале и медленно продвигается к концу. k
указывает на самый последний элемент. j
указывает на то, где i
началось. Мы итеративно пытаемся суммировать элементы по их соответствующим индексам, и каждый раз происходит одно из следующего:
- Сумма точно правильная! Мы нашли ответ.
- Сумма была слишком мала. Подойдите
j
ближе к концу, чтобы выбрать следующий по величине номер.
- Сумма была слишком большой. Подойдите
k
ближе к началу, чтобы выбрать следующее наименьшее число.
Для каждого i
указатели j
и k
будут постепенно приближаться друг к другу. В конце концов они будут проходить друг друга, и в этот момент нам не нужно ничего для этого пробовать i
, поскольку мы суммируем одни и те же элементы, просто в другом порядке. После этого мы попробуем следующее i
и повторим.
В конце концов, мы либо исчерпаем полезные возможности, либо найдем решение. Вы можете видеть, что это O (n 2 ), поскольку мы выполняем внешний цикл O (n) раз и выполняем внутренний цикл O (n) раз. Это можно сделать субквадратично, если вы действительно придумаете, представив каждое целое число как битовый вектор и выполнив быстрое преобразование Фурье, но это выходит за рамки этого ответа.
Примечание. Поскольку это вопрос интервью, я немного обманываю: этот алгоритм позволяет выбирать один и тот же элемент несколько раз. То есть (-1, -1, 2) будет правильным решением, как (0, 0, 0). Он также находит только точные ответы, а не самый близкий ответ, как упоминается в заголовке. В качестве упражнения для читателя я дам вам понять, как заставить его работать только с отдельными элементами (но это очень простое изменение) и точными ответами (что также является простым изменением).