web-dev-qa-db-fra.com

Trouver des doublons dans O(n) temps et O(1) espace

Entrée: Soit un tableau de n éléments contenant des éléments de 0 à n-1, l’un ou l’autre de ces nombres apparaissant autant de fois que nécessaire. 

Objectif: Trouver ces nombres répétitifs dans O(n) en utilisant uniquement l’espace mémoire constant.

Par exemple, supposons que n soit 7 et que array soit {1, 2, 3, 1, 3, 0, 6}, la réponse devrait être 1 & 3 . J'ai vérifié des questions similaires ici mais les réponses utilisaient des structures de données telles que HashSet etc. 

Un algorithme efficace pour le même? 

118
Zaki

C'est ce que j'ai proposé, qui n'exige pas le bit de signe supplémentaire:

for i := 0 to n - 1
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 0 to n - 1
    if A[i] != i then 
        print A[i]
    end if
end for

La première boucle permute le tableau de sorte que si l'élément x est présent au moins une fois, l'une de ces entrées sera à la position A[x].

Notez qu’il n’apparaîtra peut-être pas à première vue O(n), mais c’est vrai - bien qu’il ait une boucle imbriquée, il s’exécute quand même à O(N). Un échange n'a lieu que s'il existe une i telle que A[i] != i et que chaque échange définit au moins un élément tel que A[i] == i, où ce n'était pas vrai auparavant. Cela signifie que le nombre total de swaps (et donc le nombre total d'exécutions du corps de la boucle while) est au plus N-1.

La seconde boucle imprime les valeurs de x pour lesquelles A[x] n'est pas égal à x - car la première boucle garantit que si x existe au moins une fois dans le tableau, l'une de ces instances sera à A[x], cela signifie x qui ne sont pas présents dans le tableau.

(lien Ideone pour que vous puissiez jouer avec)

162
caf

La réponse brillante de caf imprime chaque nombre qui apparaît k fois dans le tableau k-1 fois. C'est un comportement utile, mais la question appelle sans doute que chaque duplicata ne soit imprimé qu'une seule fois, et il évoque la possibilité de le faire sans dépasser les limites linéaires d'espace/temps constant. Cela peut être fait en remplaçant sa deuxième boucle par le pseudocode suivant:

for (i = 0; i < N; ++i) {
    if (A[i] != i && A[A[i]] == A[i]) {
        print A[i];
        A[A[i]] = i;
    }
}

Cela exploite la propriété qu'après la première boucle, si une valeur m apparaît plusieurs fois, il est alors garanti que l'une de ces apparences se trouve dans la position correcte, à savoir A[m]. Si nous sommes prudents, nous pouvons utiliser cet emplacement "maison" pour stocker des informations indiquant si des copies ont déjà été imprimées ou non.

Dans la version de caf, alors que nous parcourions le tableau, A[i] != i impliquait que A[i] était un doublon. Dans ma version, je m'appuie sur un invariant légèrement différent: A[i] != i && A[A[i]] == A[i] implique que A[i] est un doublon que nous n'avions pas vu auparavant. (Si vous supprimez la partie "que nous n'avons pas vue auparavant", le reste peut être perçu comme étant implicite par la vérité de l'invariant de caf et par la garantie que tous les doublons en ont une copie à la maison.) au début (après la fin de la première boucle de caf) et je montre ci-dessous qu'il est maintenu après chaque étape.

Au fur et à mesure que nous parcourons le tableau, le succès de la partie A[i] != i du test implique que A[i]pourrait être un doublon jamais vu auparavant. Si nous ne l'avons pas vu auparavant, nous nous attendons à ce que l'emplacement de A[i] se désigne lui-même - c'est ce qui a été testé pour la deuxième moitié de la condition if. Si tel est le cas, nous l’imprimons et modifions l’emplacement de la maison pour renvoyer à cette première copie trouvée, créant ainsi un "cycle" en 2 étapes.

Pour voir que cette opération ne modifie pas notre invariant, supposons m = A[i] pour une position particulière i satisfaisant A[i] != i && A[A[i]] == A[i]. Il est évident que la modification apportée (A[A[i]] = i) empêchera que d'autres occurrences non-domestiques de m soient reproduites en double en faisant échouer la 2e moitié de leurs conditions if, mais fonctionnera lorsque i arrivera à l'emplacement d'origine, m? Oui, parce que maintenant, même si, à cette nouvelle i, nous constatons que la 1ère moitié de la condition if, A[i] != i, est vraie, la 2ème moitié vérifie si l'emplacement vers lequel elle pointe est d'origine et constate que ce n'est pas le cas. . Dans cette situation, nous ne savons plus si m ou A[m] était la valeur dupliquée, mais nous savons que de toute façon, cela a déjà été rapporté, car il est garanti que ces 2 cycles n'apparaîtront pas dans le résultat de la 1ère boucle de caf . (Notez que si m != A[m], alors exactement l'une des m et A[m] apparaît plusieurs fois, et l'autre ne se produit pas du tout.)

33
j_random_hacker

Voici le pseudocode

for i <- 0 to n-1:
   if (A[abs(A[i])]) >= 0 :
       (A[abs(A[i])]) = -(A[abs(A[i])])
   else
      print i
end for

Exemple de code en C++

22
Prasoon Saurav

Pour N relativement petit, nous pouvons utiliser des opérations div/mod

n.times do |i|
  e = a[i]%n
  a[e] += n
end

n.times do |i| 
  count = a[i]/n
  puts i if count > 1
end

Pas C/C++ mais quand même

http://ideone.com/GRZPI

1
hoha

Pas vraiment joli mais au moins, il est facile de voir les propriétés O(N) et O(1). Essentiellement, nous balayons le tableau et, pour chaque nombre, nous voyons si la position correspondante a été marquée déjà plusieurs fois (N) ou déjà plusieurs fois (N + 1). S'il est signalé déjà vu une fois, nous l'imprimons et le signalons déjà vu plusieurs fois. S'il n'est pas marqué, nous le signalons déjà vu une fois et nous déplaçons la valeur d'origine de l'index correspondant vers la position actuelle (le marquage est une opération destructive).

for (i=0; i<a.length; i++) {
  value = a[i];
  if (value >= N)
    continue;
  if (a[value] == N)  {
    a[value] = N+1; 
    print value;
  } else if (a[value] < N) {
    if (value > i)
      a[i--] = a[value];
    a[value] = N;
  }
}

ou, mieux encore (plus rapide malgré la double boucle):

for (i=0; i<a.length; i++) {
  value = a[i];
  while (value < N) {
    if (a[value] == N)  {
      a[value] = N+1; 
      print value;
      value = N;
    } else if (a[value] < N) {
      newvalue = value > i ? a[value] : N;
      a[value] = N;
      value = newvalue;
    }
  }
}
1
CAFxX

Supposons que nous présentions ce tableau comme une structure de données de graphe unidirectionnelle - chaque nombre est un sommet et son index dans le tableau pointe vers un autre sommet formant un bord du graphe. 

Pour encore plus de simplicité, nous avons les indices 0 à n-1 et des nombres allant de 0..n-1 . par exemple. 

   0  1  2  3  4 
 a[3, 2, 4, 3, 1]

0 (3) -> 3(3) est un cycle.

Réponse: Il suffit de parcourir le tableau en s’appuyant sur des indices. si un [x] = un [y] alors c'est un cycle et donc dupliquer. Passez à l'index suivant et continuez encore et ainsi de suite jusqu'à la fin d'un tableau. Complexité: O(n) temps et O(1) espace.

1
Ivan Voroshilin

Une solution en C est:

#include <stdio.h>

int finddup(int *arr,int len)
{
    int i;
    printf("Duplicate Elements ::");
    for(i = 0; i < len; i++)
    {
        if(arr[abs(arr[i])] > 0)
          arr[abs(arr[i])] = -arr[abs(arr[i])];
        else if(arr[abs(arr[i])] == 0)
        {
             arr[abs(arr[i])] = - len ;
        }
        else
          printf("%d ", abs(arr[i]));
    }

}
int main()
{   
    int arr1[]={0,1,1,2,2,0,2,0,0,5};
    finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
    return 0;
}

C'est O(n) temps et O(1) complexité de l'espace.

1
Anshul garg

J'ai créé un exemple d'application de terrain de jeu dans Swift pour rechercher les doublons dans 0(n) complexité temporelle et espace supplémentaire constant . Veuillez consulter l'url Recherche de doublons

IMP La solution ci-dessus a fonctionné lorsqu'un tableau contient des éléments de 0 à n-1, l'un ou l'autre de ces nombres apparaissant autant de fois que nécessaire.

0
CrazyPro007

L'algorithme peut être facilement vu dans la fonction C suivante. La récupération du tableau d'origine, bien que non requise, sera possible en prenant chaque entrée modulo n.

void print_repeats(unsigned a[], unsigned n)
{
    unsigned i, _2n = 2*n;
    for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
    for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
    putchar('\n');
}

 

Ideone Link pour les tests.

0
Apshir

Un petit code python pour démontrer la méthode de caf ci-dessus:

a = [3, 1, 1, 0, 4, 4, 6] 
n = len(a)
for i in range(0,n):
    if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
    if a[i] != i: print( a[i] )
0
vine'th
static void findrepeat()
{
    int[] arr = new int[7] {0,2,1,0,0,4,4};

    for (int i = 0; i < arr.Length; i++)
    {
        if (i != arr[i])
        {
            if (arr[i] == arr[arr[i]])
            {
                Console.WriteLine(arr[i] + "!!!");
            }

            int t = arr[i];
            arr[i] = arr[arr[i]];
            arr[t] = t;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();

    for (int j = 0; j < arr.Length; j++)
    {
        if (j == arr[j])
        {
            arr[j] = 1;
        }
        else
        {
            arr[arr[j]]++;
            arr[j] = 0;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();
}
0
Eli