web-dev-qa-db-fra.com

Trouver trois éléments dans un tableau dont la somme est la plus proche d'un nombre donné

Étant donné un tableau d'entiers, A1, UNE2, ..., UNEn, y compris les négatifs et les positifs, et un autre entier S. Maintenant, nous devons trouver trois entiers différents dans le tableau, dont la somme est la plus proche de l’entier donné S. S'il existe plusieurs solutions, chacune d’elles est ok.

Vous pouvez supposer que tous les entiers sont compris dans la plage int32_t et qu'aucun calcul arithmétique ne se produira lors du calcul de la somme. S n'a rien de spécial mais un nombre choisi au hasard.

Existe-t-il un algorithme efficace autre que la recherche par force brute pour trouver les trois entiers?

155
ZelluX

Existe-t-il un algorithme efficace autre que la recherche par force brute pour trouver les trois entiers?

Oui; nous pouvons résoudre cela en O (n2) temps! Tout d'abord, considérez que votre problème P peut être formulé de manière équivalente d'une manière légèrement différente, ce qui élimine la nécessité d'une "valeur cible":

problème original P: Soit un tableau A de n entier et une valeur cible S, existe-t-il un 3- Tuple de A qui résume en S?

problème modifié P': Soit un tableau A de n nombres entiers, existe-t-il un 3-Tuple de A dont la somme est égale à zéro?

Notez que vous pouvez partir de cette version du problème P' de P en soustrayant votre S/3 de chaque élément de A, mais vous n’avez plus besoin de la valeur cible.

Clairement, si nous testions simplement tous les 3-tuples possibles, nous résoudrions le problème dans O (n3) - c'est la ligne de base de la force brute. Est-il possible de faire mieux? Et si on choisissait les n-uplets de manière plus intelligente?

Premièrement, nous investissons un peu de temps pour trier le tableau, ce qui nous coûte une pénalité initiale de O (n log n). Maintenant, nous exécutons cet algorithme:

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.
}

Cet algorithme fonctionne en plaçant trois pointeurs, i, j et k à divers endroits du tableau. i commence au début et avance lentement jusqu'à la fin. k pointe vers le tout dernier élément. j pointe vers l'endroit où i a commencé à. Nous essayons de façon itérative de faire la somme des éléments à leurs indices respectifs, et chaque fois qu’un des événements suivants se produit:

  • La somme est tout à fait juste! Nous avons trouvé la réponse.
  • La somme était trop petite. Déplacez j plus près de la fin pour sélectionner le plus grand nombre suivant.
  • La somme était trop grosse. Déplacez k plus près du début pour sélectionner le plus petit nombre suivant.

Pour chaque i, les pointeurs de j et k se rapprochent progressivement. Ils finiront par se croiser, et à ce stade, nous n’avons plus besoin d’essayer autre chose pour ce i, car nous additionnerions les mêmes éléments, mais dans un ordre différent. Après cela, nous essayons le prochain i et répétons.

Éventuellement, nous épuiserons les possibilités utiles ou trouverons la solution. Vous pouvez voir que c'est O (n2) puisque nous exécutons la boucle externe O(n) fois et que nous exécutons la boucle interne O(n) fois. Il est possible de le faire de manière sub-quadratique si vous avez vraiment envie, en représentant chaque entier sous forme de vecteur de bits et en effectuant une transformation de Fourier rapide, mais cela dépasse le cadre de cette réponse.


Note: Puisqu'il s'agit d'une question d'entrevue, j'ai un peu triché ici: cet algorithme permet de sélectionner le même élément plusieurs fois. C'est-à-dire que (-1, -1, 2) serait une solution valide, tout comme (0, 0, 0). Il ne trouve également que les réponses exactes exactes , et non la réponse la plus proche, comme le titre le mentionne. À titre d’exercice pour le lecteur, je vous laisserai comprendre comment le faire fonctionner avec des éléments distincts uniquement (mais c’est un changement très simple) et des réponses exactes (qui est également un changement simple).

185
John Feminella

c'est certainement une meilleure solution car elle est plus lisible et donc moins sujette aux erreurs. Le seul problème est que nous devons ajouter quelques lignes de code pour éviter la sélection multiple d'un élément.

Une autre solution O (n ^ 2) (en utilisant un hashset).

// 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])
28
Cem

La solution de John Feminella a un bogue.

À la ligne

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

Nous devons vérifier si i, j, k sont tous distincts. Sinon, si mon élément cible est 6 et si mon tableau d'entrée contient {3,2,1,7,9,0,-4,6}. Si j’imprime les nuplets qui totalisent 6, j’obtiendrais aussi 0,0,6 en sortie. Pour éviter cela, nous devons modifier la condition de cette manière.

if ((A[i] + A[j] + A[k] == 0) && (i!=j) && (i!=k) && (j!=k)) return (A[i], A[j], A[k])
6
Rambo7

Que diriez-vous de quelque chose comme ceci, qui est 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.
        }
    }

Ceci trouve si la somme de 3 éléments est exactement égale à votre nombre. Si vous voulez plus proche, vous pouvez le modifier pour rappeler le plus petit delta (différence entre votre nombre de triplet actuel) et à la fin imprimer le triplet correspondant au plus petit delta.

6
codaddict

Notez que nous avons un tableau trié. Cette solution est similaire à la solution de John, à la différence qu’elle recherche la somme et ne répète pas le même élément.

#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));
}
5
Pasada

Voici le code 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;
}
3
Peter Lee

Très simple solution N ^ 2 * logN: triez le tableau d’entrée, puis parcourez toutes les paires Aje, UNEj (N ^ 2 fois), et pour chaque paire, vérifiez si (S - Aje - UNEj) est dans le tableau (heure de logN).

Une autre solution O (S * N) utilise l'approche classique programmation dynamique .

En bref:

Créez un tableau 2-D V [4] [S + 1]. Remplissez-le de telle sorte que:

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

V 1 [Aje] = 1 pour tout i, V 1 [x] = 0 pour tout autre x

V [2] [Aje + Unj] = 1, pour tout i, j. V [2] [x] = 0 pour tous les autres x

V [3] [somme de 3 éléments quelconques] = 1.

Pour le remplir, parcourez Aje, pour chaque Aje parcourez le tableau de droite à gauche.

2
Olexiy

Ceci peut être résolu efficacement en O (n log (n)) comme suit. Je donne une solution qui indique si la somme de trois nombres est égale à un nombre donné.

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
Raju Singh

Voici le programme dans Java qui est 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);
    }
}
1
M Sach

Réduction: Je pense que la solution de @John Feminella O(n2) est très élégante. Nous pouvons toujours réduire le A [n] dans lequel rechercher Tuple. En observant A [k] tel que les éléments seraient dans A [0] - A [k], lorsque notre tableau de recherche est énorme et que SUM (s) est vraiment petit.

Un [0] est le minimum: - Tableau trié par ordre croissant.

s = 2A [0] + A [k]: Étant donné s et A [], nous pouvons trouver A [k] à l'aide d'une recherche binaire dans le temps log (n).

0
d1val

Une autre solution qui vérifie et échoue tôt:

public boolean solution(int[] input) {
        int length = input.length;

        if (length < 3) {
            return false;
        }

        // x + y + z = 0  => -z = x + y
        final Set<Integer> z = new HashSet<>(length);
        int zeroCounter = 0, sum; // if they're more than 3 zeros we're done

        for (int element : input) {
            if (element < 0) {
                z.add(element);
            }

            if (element == 0) {
                ++zeroCounter;
                if (zeroCounter >= 3) {
                    return true;
                }
            }
        }

        if (z.isEmpty() || z.size() == length || (z.size() + zeroCounter == length)) {
            return false;
        } else {
            for (int x = 0; x < length; ++x) {
                for (int y = x + 1; y < length; ++y) {
                    sum = input[x] + input[y]; // will use it as inverse addition
                    if (sum < 0) {
                        continue;
                    }
                    if (z.contains(sum * -1)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

J'ai ajouté quelques tests unitaires ici: GivenArrayReturnTrueIfThreeElementsSumZeroTest .

Si l'ensemble utilise trop d'espace, je peux facilement utiliser un Java.util.BitSet qui utilisera O(n/w) espace .

0
moxi

Le problème peut être résolu dans O (n ^ 2) en étendant le problème de la somme 2 avec des modifications mineures. A est le vecteur contenant les éléments et B est la somme requise.

int Solution :: threeSumClosest (vecteur & 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;
0
Anurag Semwal