it-swarm.com.de

Duplikate finden in O(n) Zeit und O(1) Platz

Eingabe: Gegeben ein Array von n Elementen, das Elemente von 0 bis n-1 enthält, wobei eine dieser Zahlen beliebig oft vorkommt. 

Ziel: Um diese sich wiederholenden Zahlen in O(n) zu finden und nur konstanten Speicherplatz zu verwenden.

Zum Beispiel sei n 7 und das Array sei {1, 2, 3, 1, 3, 0, 6}, die Antwort sollte 1 und 3 sein .. Ich habe ähnliche Fragen geprüft, aber die Antworten verwendeten einige Datenstrukturen wie HashSet usw. 

Jeder effiziente Algorithmus für das gleiche? 

118
Zaki

Das ist, was ich mir ausgedacht habe, was das zusätzliche Vorzeichen nicht erfordert:

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

Die erste Schleife permutiert das Array, sodass einer der Einträge an Position A[x] steht, wenn das Element x mindestens einmal vorhanden ist.

Beachten Sie, dass es auf den ersten Blick möglicherweise nicht nach O(n) aussieht, aber - obwohl es eine verschachtelte Schleife hat, läuft es immer noch in O(N) time. Ein Swap tritt nur dann auf, wenn es eine i wie A[i] != i gibt, und jeder Swap setzt mindestens ein Element wie A[i] == i, bei dem das vorher nicht wahr war. Dies bedeutet, dass die Gesamtzahl der Swaps (und damit die Gesamtzahl der Ausführungen des while-Schleifenrumpfes) höchstens N-1 beträgt.

Die zweite Schleife gibt die Werte von x aus, für die A[x] nicht gleich x ist. Da die erste Schleife garantiert, dass x mindestens einmal im Array vorhanden ist, befindet sich eine dieser Instanzen bei A[x]. Dies bedeutet, dass diese Werte von gedruckt werden x die im Array nicht vorhanden sind.

(Ideone-Link, damit du damit spielen kannst)

162
caf

die brillante Antwort des Cafés druckt jede Zahl, die k-mal im Array erscheint, k-1 mal aus. Das ist ein nützliches Verhalten, aber die Frage verlangt wohl, dass jedes Duplikat nur einmal gedruckt wird, und er spielt auf die Möglichkeit an, dies zu tun, ohne die linearen Grenzen von Zeit und konstantem Raum zu sprengen. Dies kann durch Ersetzen seiner zweiten Schleife durch den folgenden Pseudocode erfolgen:

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

Dabei wird die Eigenschaft ausgenutzt, dass nach dem Ausführen der ersten Schleife, wenn ein Wert m mehr als einmal vorkommt, einer dieser Erscheinungen garantiert an der richtigen Position ist, nämlich A[m]. Wenn wir vorsichtig sind, können wir diesen "Heimatort" verwenden, um Informationen darüber zu speichern, ob bereits Duplikate gedruckt wurden oder nicht.

In der caf-Version implizierte A[i] != i beim Durchlaufen des Arrays, dass A[i] ein Duplikat ist. In meiner Version verlasse ich mich auf eine etwas andere Invariante: dass A[i] != i && A[A[i]] == A[i] impliziert, dass A[i] ein Duplikat ist das wir vorher nicht gesehen haben. (Wenn Sie den Teil "Das haben wir noch nicht gesehen" weglassen, wird der Rest durch die Wahrheit der Caf-Invarianten impliziert und durch die Garantie, dass alle Duplikate eine Kopie an einem Heimatort haben.) Diese Eigenschaft gilt für Der Anfang (nachdem die erste Runde des Cafés beendet ist) und ich zeigen, dass er nach jedem Schritt beibehalten wird.

Wenn wir das Array durchgehen, impliziert der Erfolg des A[i] != i -Teils des Tests, dass A[i]könnte ein Duplikat sein, das zuvor noch nicht gesehen wurde. Wenn wir es noch nicht gesehen haben, dann erwarten wir, dass der Heimatort von A[i] auf sich selbst zeigt - das ist es, worauf die zweite Hälfte der Bedingung if hinweist. Wenn dies der Fall ist, drucken wir es aus und ändern den Heimatort, um auf dieses zuerst gefundene Duplikat zurückzugreifen, wodurch ein zweistufiger "Zyklus" erstellt wird.

Um zu sehen, dass diese Operation unsere Invariante nicht verändert, nehmen wir m = A[i] für eine bestimmte Position i an, die A[i] != i && A[A[i]] == A[i] erfüllt. Es ist offensichtlich, dass die von uns vorgenommene Änderung (A[A[i]] = i) dazu dient, zu verhindern, dass andere Nicht-Home-Vorkommen von m als Duplikate ausgegeben werden, indem die zweite Hälfte ihrer if -Bedingungen fehlschlägt. Aber wird es funktionieren, wenn i am Heimatort ankommt, m? Ja, das wird es, denn jetzt, obwohl wir bei diesem neuen i feststellen, dass die erste Hälfte der if Bedingung, A[i] != i, wahr ist, testet die zweite Hälfte, ob die Position darauf hinweist to ist ein Heimatort und stellt fest, dass dies nicht der Fall ist. In dieser Situation wissen wir nicht mehr, ob m oder A[m] der doppelte Wert war, aber wir wissen, dass in beiden Fällen es wurde bereits berichtet, da diese 2 Zyklen vorliegen garantiert nicht im Ergebnis der ersten Runde des Cafés. (Beachten Sie, dass bei m != A[m] genau einer von m und A[m] mehrmals vorkommt und der andere überhaupt nicht.)

33
j_random_hacker

Hier ist der 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

Beispielcode in C++

22
Prasoon Saurav

Für relativ kleines N können wir div/mod-Operationen verwenden

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

Nicht C/C++, aber trotzdem

http://ideone.com/GRZPI

1
hoha

Nicht wirklich hübsch, aber zumindest sind die Eigenschaften O(N) und O(1) leicht zu erkennen. Grundsätzlich scannen wir das Array und sehen für jede Zahl, ob die entsprechende Position bereits einmal (einmalig gesehen) (N) oder bereits mehrfach gesehen (N + 1) markiert wurde. Wenn es bereits einmal gesehen wurde, drucken wir es und kennzeichnen es bereits mehrmals. Wenn es nicht markiert ist, markieren wir es bereits einmal und verschieben den ursprünglichen Wert des entsprechenden Index an die aktuelle Position (Flaggen ist eine destruktive Operation).

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

oder noch besser (schneller trotz Doppelschleife):

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

Nehmen wir an, wir stellen dieses Array als unidirektionale Diagrammdatenstruktur vor. Jede Zahl ist ein Scheitelpunkt und sein Index im Array zeigt auf einen anderen Scheitelpunkt, der einen Rand des Diagramms bildet. 

Zur Vereinfachung haben wir die Indizes 0 bis n-1 und einen Zahlenbereich von 0..n-1 . z.B. 

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

0 (3) -> 3(3) ist ein Zyklus.

Antwort: Überqueren Sie einfach das Array anhand von Indizes. Wenn a [x] = a [y] dann ist es ein Zyklus und somit ein Duplikat. Fahren Sie mit dem nächsten Index fort und fahren Sie fort bis zum Ende eines Arrays. Komplexität: O(n) Zeit und O(1) Raum.

1
Ivan Voroshilin

Eine Lösung in C ist:

#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;
}

Es ist O(n) Zeit und O(1) Raumkomplexität.

1
Anshul garg

In Swift habe ich eine Beispiel-Spielplatz-App erstellt, mit der Duplikate in 0(n) Zeitkomplexität und konstantem zusätzlichen Platz finden kannst ..__ Bitte überprüfe die URL Finding Duplicates

IMP Die obige Lösung funktionierte, wenn ein Array Elemente von 0 bis n-1 enthält, wobei eine dieser Zahlen beliebig oft vorkommt.

0
CrazyPro007

Der Algorithmus ist in der folgenden C-Funktion leicht zu sehen. Das Abrufen des ursprünglichen Arrays ist zwar nicht erforderlich, aber jeder Eintrag modulo n ist möglich.

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 zum Testen.

0
Apshir

Ein kleiner Python-Code, um die oben beschriebene Methode von caf zu demonstrieren:

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