Нахождение трех элементов в массиве, сумма которого ближе всего к данному числу - PullRequest
152 голосов
/ 15 января 2010

Дан массив целых чисел, A 1 , A 2 , ..., A n , включая негативы и позитивы, и еще одно целое число S. Теперь нам нужно найти три различных целых числа в массиве, сумма которых ближе всего к данному целому числу S. Если существует более одного решения, любое из них в порядке.

Можно предположить, что все целые числа находятся в диапазоне int32_t, и при вычислении суммы не произойдет арифметического переполнения. S - это не что иное, как случайно выбранное число.

Есть ли эффективный алгоритм, кроме поиска методом грубой силы, для нахождения трех целых чисел?

Ответы [ 13 ]

182 голосов
/ 15 января 2010

Есть ли эффективный алгоритм, кроме поиска методом грубой силы, для нахождения трех целых чисел?

Да; мы можем решить это за 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). Он также находит только точные ответы, а не самый близкий ответ, как упоминается в заголовке. В качестве упражнения для читателя я дам вам понять, как заставить его работать только с отдельными элементами (но это очень простое изменение) и точными ответами (что также является простым изменением).

28 голосов
/ 19 февраля 2012

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

Другое решение O (n ^ 2) (с использованием хэш-набора).

// K is the sum that we are looking for
for i 1..n
    int s1 = K - A[i]
    for j 1..i
        int s2 = s1 - A[j]
        if (set.contains(s2))
            print the numbers
    set.add(A[i])
6 голосов
/ 27 августа 2013

В решении Джона Феминеллы есть ошибка.

На линии

if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])

Нам нужно проверить, различны ли i, j, k. В противном случае, если мой целевой элемент 6 и если мой входной массив содержит {3,2,1,7,9,0,-4,6}. Если я распечатаю кортежи с суммой 6, то я также получу 0,0,6 как вывод. Чтобы избежать этого, нам нужно изменить условие следующим образом.

if ((A[i] + A[j] + A[k] == 0) && (i!=j) && (i!=k) && (j!=k)) return (A[i], A[j], A[k])
6 голосов
/ 15 января 2010

Как насчет чего-то подобного, а это O (n ^ 2)

for(each ele in the sorted array)
{
    ele = arr[i] - YOUR_NUMBER;
    let front be the pointer to the front of the array;
    let rear be the pointer to the rear element of the array.;

    // till front is not greater than rear.                    
    while(front <= rear)
    {
        if(*front + *rear == ele)
        {
            print "Found triplet "<<*front<<","<<*rear<<","<<ele<<endl;
            break;
        }
        else
        {
            // sum is > ele, so we need to decrease the sum by decrementing rear pointer.
            if((*front + *rear) > ele)
                decrement rear pointer.
            // sum is < ele, so we need to increase the sum by incrementing the front pointer.
            else
                increment front pointer.
        }
    }

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

5 голосов
/ 23 октября 2010

Обратите внимание, что у нас есть отсортированный массив. Это решение похоже на решение Джона только в том, что оно ищет сумму и не повторяет один и тот же элемент.

#include <stdio.h>;

int checkForSum (int arr[], int len, int sum) { //arr is sorted
    int i;
    for (i = 0; i < len ; i++) {
        int left = i + 1;
        int right = len - 1;
        while (right > left) {
            printf ("values are %d %d %d\n", arr[i], arr[left], arr[right]);
            if (arr[right] + arr[left] + arr[i] - sum == 0) {
                printf ("final values are %d %d %d\n", arr[i], arr[left], arr[right]);
                return 1;
            }
            if (arr[right] + arr[left] + arr[i] - sum > 0)
                right--;
            else
                left++;
        }
    }
    return -1;
}
int main (int argc, char **argv) {
    int arr[] = {-99, -45, -6, -5, 0, 9, 12, 16, 21, 29};
    int sum = 4;
    printf ("check for sum %d in arr is %d\n", sum, checkForSum(arr, 10, sum));
}
3 голосов
/ 22 сентября 2013

Вот код C ++:

bool FindSumZero(int a[], int n, int& x, int& y, int& z)
{
    if (n < 3)
        return false;

    sort(a, a+n);

    for (int i = 0; i < n-2; ++i)
    {
        int j = i+1;
        int k = n-1;

        while (k >= j)
        {
            int s = a[i]+a[j]+a[k];

            if (s == 0 && i != j && j != k && k != i)
            {
                x = a[i], y = a[j], z = a[k];
                return true;
            }

            if (s > 0)
                --k;
            else
                ++j;
        }
    }

    return false;
}
2 голосов
/ 15 января 2010

Очень простое решение N ^ 2 * logN: отсортируйте входной массив, затем просмотрите все пары A i , A j (N ^ 2 раз) и для каждой пары проверьте, находится ли (S - A i - A j ) в массиве (время logN).

Другое решение O (S * N) использует классический подход динамического программирования .

Короче говоря:

Создать двумерный массив V [4] [S + 1]. Заполните его так, чтобы:

V [0] [0] = 1, V [0] [x] = 0;

V 1 [A i ] = 1 для любого i, V 1 [x] = 0 для всех остальных x

V [2] [A i + A j ] = 1 для любого i, j. V [2] [x] = 0 для всех остальных x

V [3] [сумма любых 3 элементов] = 1.

Чтобы заполнить его, выполните итерацию A i , для каждого A i итерацию по массиву справа налево.

1 голос
/ 25 октября 2016

Это может быть эффективно решено в O (n log (n)) следующим образом. Я даю решение, которое сообщает, равна ли сумма любых трех чисел данному числу.

import java.util.*;
public class MainClass {
        public static void main(String[] args) {
        int[] a = {-1, 0, 1, 2, 3, 5, -4, 6};
        System.out.println(((Object) isThreeSumEqualsTarget(a, 11)).toString());
}

public static boolean isThreeSumEqualsTarget(int[] array, int targetNumber) {

    //O(n log (n))
    Arrays.sort(array);
    System.out.println(Arrays.toString(array));

    int leftIndex = 0;
    int rightIndex = array.length - 1;

    //O(n)
    while (leftIndex + 1 < rightIndex - 1) {
        //take sum of two corners
        int sum = array[leftIndex] + array[rightIndex];
        //find if the number matches exactly. Or get the closest match.
        //here i am not storing closest matches. You can do it for yourself.
        //O(log (n)) complexity
        int binarySearchClosestIndex = binarySearch(leftIndex + 1, rightIndex - 1, targetNumber - sum, array);
        //if exact match is found, we already got the answer
        if (-1 == binarySearchClosestIndex) {
            System.out.println(("combo is " + array[leftIndex] + ", " + array[rightIndex] + ", " + (targetNumber - sum)));
            return true;
        }
        //if exact match is not found, we have to decide which pointer, left or right to move inwards
        //we are here means , either we are on left end or on right end
        else {

            //we ended up searching towards start of array,i.e. we need a lesser sum , lets move inwards from right
            //we need to have a lower sum, lets decrease right index
            if (binarySearchClosestIndex == leftIndex + 1) {
                rightIndex--;
            } else if (binarySearchClosestIndex == rightIndex - 1) {
                //we need to have a higher sum, lets decrease right index
                leftIndex++;
            }
        }
    }
    return false;
}

public static int binarySearch(int start, int end, int elem, int[] array) {
    int mid = 0;
    while (start <= end) {
        mid = (start + end) >>> 1;
        if (elem < array[mid]) {
            end = mid - 1;
        } else if (elem > array[mid]) {
            start = mid + 1;
        } else {
            //exact match case
            //Suits more for this particular case to return -1
            return -1;
        }
    }
    return mid;
}
}
1 голос
/ 06 августа 2016

Вот программа в Java, которая O (N ^ 2)

import java.util.Stack;


public class GetTripletPair {

    /** Set a value for target sum */
    public static final int TARGET_SUM = 32;

    private Stack<Integer> stack = new Stack<Integer>();

    /** Store the sum of current elements stored in stack */
    private int sumInStack = 0;
    private int count =0 ;


    public void populateSubset(int[] data, int fromIndex, int endIndex) {

        /*
        * Check if sum of elements stored in Stack is equal to the expected
        * target sum.
        * 
        * If so, call print method to print the candidate satisfied result.
        */
        if (sumInStack == TARGET_SUM) {
            print(stack);
        }

        for (int currentIndex = fromIndex; currentIndex < endIndex; currentIndex++) {

            if (sumInStack + data[currentIndex] <= TARGET_SUM) {
                ++count;
                stack.push(data[currentIndex]);
                sumInStack += data[currentIndex];

                /*
                * Make the currentIndex +1, and then use recursion to proceed
                * further.
                */
                populateSubset(data, currentIndex + 1, endIndex);
                --count;
                sumInStack -= (Integer) stack.pop();
            }else{
            return;
        }
        }
    }

    /**
    * Print satisfied result. i.e. 15 = 4+6+5
    */

    private void print(Stack<Integer> stack) {
        StringBuilder sb = new StringBuilder();
        sb.append(TARGET_SUM).append(" = ");
        for (Integer i : stack) {
            sb.append(i).append("+");
        }
        System.out.println(sb.deleteCharAt(sb.length() - 1).toString());
    }

    private static final int[] DATA = {4,13,14,15,17};

    public static void main(String[] args) {
        GetAllSubsetByStack get = new GetAllSubsetByStack();
        get.populateSubset(DATA, 0, DATA.length);
    }
}
0 голосов
/ 13 июля 2017

Проблема может быть решена в O (n ^ 2) путем расширения задачи 2-суммы с незначительными изменениями. A - вектор, содержащий элементы, а B - необходимая сумма.

int Solution :: threeSumClosest (vector & A, int B) {

sort(A.begin(),A.end());

int k=0,i,j,closest,val;int diff=INT_MAX;

while(k<A.size()-2)
{
    i=k+1;
    j=A.size()-1;

    while(i<j)
    {
        val=A[i]+A[j]+A[k];
        if(val==B) return B;
        if(abs(B-val)<diff)
        {
            diff=abs(B-val);
            closest=val;
        }
        if(B>val)
        ++i;
        if(B<val) 
        --j;
    }
    ++k;

}
return closest;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...