web-dev-qa-db-fra.com

Déterminer s’il existe ou non deux éléments dans l’ensemble S dont la somme correspond exactement à x - solution correcte?

Tiré de Introduction aux algorithmes

Décrivez un algorithme de temps Θ (nlg n) qui, étant donné un ensemble S de n entiers et un autre entier x, détermine s'il existe ou non deux éléments dans S dont la somme est exactement x.

C'est ma meilleure solution implémentée en Java jusqu'à présent:

    public static boolean test(int[] a, int val) {
    mergeSort(a);

    for (int i = 0; i < a.length - 1; ++i) {
        int diff = (val >= a[i]) ? val - a[i] : a[i] - val;

        if (Arrays.binarySearch(a, i, a.length, diff) >= 0) {
            return true;
        }
    }

    return false;
}

Maintenant ma première question est: Est-ce une solution correcte? Si je comprends bien, mergeSort doit effectuer le tri dans O (n lg n), la boucle doit prendre O (n lg n) (n pour l’itération multipliée par O (lg n) pour la recherche binaire, ce qui donne O n), donc cela devrait être correct.

Ma deuxième question est la suivante: existe-t-il de meilleures solutions? Est-ce que le tri du tableau est essentiel?

33
helpermethod

Votre solution semble bien. Oui, vous devez trier car c'est une condition préalable à la recherche binaire. Vous pouvez apporter une légère modification à votre logique comme suit:

public static boolean test(int[] a, int val) 
{
    Arrays.sort(a);

    int i = 0;            // index of first element.
    int j = a.length - 1; // index of last element. 

    while(i<j)
    {
        // check if the sum of elements at index i and j equals val, if yes we are done.
        if(a[i]+a[j] == val)
            return true;
        // else if sum if more than val, decrease the sum.
        else if(a[i]+a[j] > val)
            j--;
        // else if sum is less than val, increase the sum.
        else
            i++;
    }
    // failed to find any such pair..return false. 
    return false;
}
41
codaddict

Il existe une autre solution très rapide: imaginez que vous deviez résoudre ce problème en Java pour environ 1 milliard d’entiers. Vous savez qu'en Java, les nombres entiers vont de -2**31+1 à +2**31.

Créez un tableau avec 2**32 milliard de bits (500 Mo, simple à faire sur le matériel actuel).

Itérez sur votre ensemble: si vous avez un entier, définissez le bit correspondant sur 1.

O (n) jusqu'à présent.

Itérez à nouveau sur votre ensemble: pour chaque valeur, vérifiez si vous avez un bit défini sur "current val - x".

Si vous en avez un, vous retournez vrai.

Certes, il a besoin de 500 Mo de mémoire.

Mais cela doit tourner autour de toute autre solution O (n log n) si vous devez, par exemple, résoudre ce problème avec 1 milliard d’entiers.

Sur).

13
SyntaxT3rr0r
  1. C'est correct; votre algorithme s'exécutera dans le temps O (n lg n).

  2. Il existe une meilleure solution: votre logique de calcul du diff est incorrecte. Indépendamment du fait que a[i] soit supérieur ou inférieur à val, vous avez toujours besoin que diff soit val - a[i].

6
danben

Voici une solution O(n) utilisant un ensemble de hachage:

  public static boolean test(int[] a, int val) {
      Set<Integer> set = new HashSet<Integer>();

      // Look for val/2 in the array
      int c = 0;
      for(int n : a) {
        if(n*2 == val)
          ++c
      }
      if(c >= 2)
         return true; // Yes! - Found more than one

      // Now look pairs not including val/2
      set.addAll(Arrays.asList(a));
      for (int n : a) {
         if(n*2 == val)
            continue;
         if(set.contains(val - n))
            return true;
      }

      return false;
   }
5
Itay Maman

Une solution simple consiste, après le tri, à déplacer les pointeurs des deux côtés de la matrice, en recherchant les paires dont la somme est égale à x. Si la somme est trop élevée, décrémentez le pointeur de droite. Si trop bas, incrémenter celui de gauche. Si les pointeurs se croisent, la réponse est non.

4
Neal Gafter

Je pense avoir repéré un bug mineur dans votre implémentation, mais les tests devraient permettre de le découvrir rapidement.

L'approche semble valide et atteindra la performance souhaitée. Vous pouvez le simplifier en remplaçant la recherche binaire itérative par une analyse du tableau, remplaçant ainsi la recherche binaire par une recherche linéaire qui reprend là où la recherche linéaire précédente s'était arrêtée:

int j = a.length - 1;
for (int i = 0; i < a.length; i++) {
    while (a[i] + a[j] > val) {
        j--;
    }
    if (a[i] + a[j] == val) {
        // heureka!
    }
}

Cette étape est O (n). (Prouver que cela reste un exercice pour vous.) Bien sûr, l’algorithme entier prend toujours O (n log n) pour le tri par fusion.

3
meriton

Votre analyse est correcte, et oui vous devez trier le tableau sinon la recherche binaire ne fonctionne pas.

2
Sean Owen

Voici une solution alternative, en ajoutant quelques conditions supplémentaires à mergesort.

public static void divide(int array[], int start, int end, int sum) {

    if (array.length < 2 || (start >= end)) {
        return;
    }
    int mid = (start + end) >> 1; //[p+r/2]
    //divide
    if (start < end) {
        divide(array, start, mid, sum);
        divide(array, mid + 1, end, sum);
        checkSum(array, start, mid, end, sum);
    }
}

private static void checkSum(int[] array, int str, int mid, int end, int sum) {

    int lsize = mid - str + 1;
    int rsize = end - mid;
    int[] l = new int[lsize]; //init
    int[] r = new int[rsize]; //init

    //copy L
    for (int i = str; i <= mid; ++i) {
        l[i-str] = array[i];
    }
    //copy R
    for (int j = mid + 1; j <= end; ++j) {
        r[j - mid - 1] = array[j];
    }
    //SORT MERGE
    int i = 0, j = 0, k=str;
    while ((i < l.length) && (j < r.length) && (k <= end)) {
    //sum-x-in-Set modification
    if(sum == l[i] + r[j]){
        System.out.println("THE SUM CAN BE OBTAINED with the values" + l[i] + " " + r[j]);            
    }
     if (l[i] < r[j]) {
            array[k++] = l[i++];
        } else {
            array[k++] = r[j++];
        }
    }
    //left over
    while (i < l.length && k <= end) {
        array[k++] = l[i++];
          //sum-x-in-Set modification
        for(int x=i+1; x < l.length; ++x){
            if(sum == l[i] + l[x]){
                System.out.println("THE SUM CAN BE OBTAINED with the values" + l[i] + " " + l[x]);
            }
        }
    }
    while (j < r.length && k <= end) {
        array[k++] = r[j++];
          //sum-x-in-Set modification
        for(int x=j+1; x < r.length; ++x){
            if(sum == r[j] + r[x]){
                System.out.println("THE SUM CAN BE OBTAINED with the values" + r[j] + " " + r[x]);
            }
        }
    }
}

Mais la complexité de cet algorithme n’est toujours pas égale à THETA (nlogn)

0
sij