Есть ли эффективный алгоритм, кроме поиска методом грубой силы, для нахождения трех целых чисел?
Да; мы можем решить это за O (n 2 ) время! Во-первых, учтите, что ваша проблема P
может быть сформулирована эквивалентным образом несколько иным способом, что устраняет необходимость в «целевом значении»:
исходная задача P
: Учитывая массив A
из n
целых чисел и целевое значение S
, существует ли 3-кортеж из A
, который суммируется до S
измененная задача P'
: Учитывая массив A
из n
целых чисел, существует ли 3-кортеж из 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). Он также находит только точные ответы, а не самый близкий ответ, как упоминается в заголовке. В качестве упражнения для читателя я дам вам понять, как заставить его работать только с отдельными элементами (но это очень простое изменение) и точными ответами (что также является простым изменением).