Forum: PC-Programmierung Doppelten Eintrag im Array finden in C, Code schneller machen


von Johannes (Gast)


Lesenswert?

Hallo,
ich versuche mich gerade an einer Aufgabe auf codefights.com. Die 
Aufgabe möchte ich in C lösen
Die Aufgabe ist wie folgt:

Given an array a that contains only numbers in the range from 1 to 
a.length, find the first duplicate number for which the second 
occurrence has the minimal index. In other words, if there are more than 
1 duplicated numbers, return the number for which the second occurrence 
has a smaller index than the second occurrence of the other number does. 
If there are no such elements, return -1.

Example
For a = [2, 3, 3, 1, 5, 2], the output should be
firstDuplicate(a) = 3.
There are 2 duplicates: numbers 2 and 3. The second occurrence of 3 has 
a smaller index than than second occurrence of 2 does, so the answer is 
3.

For a = [2, 4, 3, 5, 1], the output should be
firstDuplicate(a) = -1.

Input/Output
[execution time limit] 0.5 seconds (c)

[input] array.integer a

Guaranteed constraints:
1 ≤ a.length ≤ 105,
1 ≤ a[i] ≤ a.length.

The element in a that occurs in the array more than once and has the 
minimal index for its second occurrence. If there are no such elements, 
return -1.
1
// Definition for arrays:
2
// typedef struct arr_##name {
3
//   int size;
4
//   type *arr;
5
// } arr_##name;
6
//
7
// arr_##name alloc_arr_##name(int len) {
8
//   arr_##name a = {len, len > 0 ? malloc(sizeof(type) * len) : NULL};
9
//   return a;
10
// }
11
//
12
//
13
int firstDuplicate(arr_integer a) {
14
    int value = -1;
15
    int index = 0;
16
    for(int x=0; x<a.size-1; x++)
17
    {
18
        for(int y=x+1; y<a.size; y++) if(a.arr[x]==a.arr[y])
19
        {
20
            value = a.arr[x]; 
21
            index = y; 
22
            a.size = y;
23
            if((y-x) == 1) return value;
24
            break;
25
        }
26
    }
27
    return value;
28
}

Der Code funktioniert auch. Allerdings bekomme ich für einen Test die 
Fehlermeldung, dass mein Code zu langsam ist.

Gibt es eine Methode, mit der ich direkt doppelte Einträge abfragen 
kann? Also dass ich nicht mit for-Schleifen den array durchgehen muss?

habe schon versucht den Code schneller zu machen
a.size = y; -> Die For-Schleife wird verkürzt, sodass nicht mehr alles 
durchgegangen werden muss
if((y-x)==1) return value; -> einen ängeren abstand als 1 gibt es nicht, 
von daher kann hier sofort alles abgebrochen werden
break; -> der rest der for-Schleife muss nicht mehr durchgegangen 
werden, da das pärchen mit diesem Wert schon gefunden wurde

Gruß
Johannes

von Thomas G. (blasebalg)


Lesenswert?

Johannes schrieb:
> Given an array a that contains only numbers in the range from 1 to
> a.length,

Der Extremwert für's Maximum ist doch bekannt ?!?
Hilfsarray erstellen mit [0-a.length]

Dein Original absuchen und im Hilfsarray markieren ob der Wert schon 
vorgekommen ist oder nicht.

von Arc N. (arc)


Lesenswert?

Thomas G. schrieb:
> Johannes schrieb:
>> Given an array a that contains only numbers in the range from 1 to
>> a.length,
>
> Der Extremwert für's Maximum ist doch bekannt ?!?
> Hilfsarray erstellen mit [0-a.length]
>
> Dein Original absuchen und im Hilfsarray markieren ob der Wert schon
> vorgekommen ist oder nicht.

In der Originalaufgabe steht meist noch Laufzeit O(n) und zusätzlicher 
Speicher O(1) ;) Also Inplace statt extra Speicher.

von Torsten (Gast)


Lesenswert?

Hi,

Zuerst sowas durch arbeiten und dann anfangen solche 'challanges' zu 
bearbeiten kann weniger frustrierend sein:

http://ls2-www.cs.tu-dortmund.de/grav/grav_files/lehre/sommer2007/dap2/skript.pdf


Das Problem ist Dein Algorithmus hat eine Komplexität von O(n²). Das 
geht auch in O(n Log(n)): sortieren mit z.B. quick sort und beim 
Vergleich bei Gleichheit abbrechen. Da der Input random ist, sollte das 
optimal sein, wenn der Input umgekehrt sortiert sein kann, würde ich 
über bottom-up heapsort nehmen und mir den kleinsten Input Index merken 
der gleich war.

Grüße
 Thorsten

von Vlad T. (vlad_tepesch)


Lesenswert?

Arc N. schrieb:
> In der Originalaufgabe steht meist noch Laufzeit O(n) und zusätzlicher
> Speicher O(1) ;) Also Inplace statt extra Speicher.

in dem fall:
das oberste bit des ints in dem array könnte man als "schonmal gesehen" 
bit deklarieren. solange das array kleiner als 2,xx Mrd einträeg hat 
(und 32bit ist) sollte es da kein problem geben.

Und die Definition von Duplikat heißt, dass das erste gefundene Pärchen 
das gesuchte ist.
Das gane lässt sich also in O(n) lösen, sofern man n bits speicher an;

sprich:
1
for (i=0; i<len; ++i){
2
  int v = a[i] & 0x7fFFffFF;
3
  bool seen = a[v] & 0x80000000;   
4
  if(seen){
5
    break; // gefunden
6
  }
7
  a[v] =  a[v] |0x80000000; 
8
}


Falls a readonly ist, dann eben doch ein separates Bitset anlegen.
Speicherbedarf also n/8 Bytes

: Bearbeitet durch User
von Scyte R. (scyte)


Lesenswert?

1
int firstDuplicate(arr_integer a) {
2
    for(int x=1; x<a.size; x++)
3
    {
4
        for(int y=0; y<x; y++) if(a.arr[x]==a.arr[y])
5
        {
6
            return  a.arr[x];
7
        }
8
    }
9
    return -1;
10
}
was hälst du davon? von vorne durch gehen, ob es die zahl schon mal gab.
Erstes Dublikat ist das Ergebnis.

Gruß Scyte

p.s. das von Vlad Tepesch geht natürlich auch, da int minimum 16bit ist 
und das größte Element wäre 105 nach Aufgabenstellung.

: Bearbeitet durch User
von Vlad T. (vlad_tepesch)


Lesenswert?

Scyte R. schrieb:
> p.s. das von Vlad Tepesch geht natürlich auch, da int minimum 16bit ist
> und das größte Element wäre 105 nach Aufgabenstellung.

oh, das hab ich ganz übersehen.
Das ist ja lächerlich. Wie wollen sie da messen, ob man da O(n) oder 
O(n³) code produziert?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arc N. schrieb:
> Thomas G. schrieb:
>> Der Extremwert für's Maximum ist doch bekannt ?!?
>> Hilfsarray erstellen mit [0-a.length]
>>
>> Dein Original absuchen und im Hilfsarray markieren ob der Wert schon
>> vorgekommen ist oder nicht.
>
> In der Originalaufgabe steht meist noch Laufzeit O(n) und zusätzlicher
> Speicher O(1) ;) Also Inplace statt extra Speicher.

Passt doch.  a.length ist nach oben durch 150 beschränkt, d.h. ein 
zusätzliches Array mit a.length Elementen verbraucht zusätzlichen 
Speicher O(1) da durch eine Konstante unabhängig von der Eingabe nach 
oben abschätzbar.

von Mikro 7. (mikro77)


Lesenswert?

Johannes schrieb:
> habe schon versucht den Code schneller zu machen

Mit zusätzlichen O(n) Speicher hat Thomas bereits die Lösung gezeigt mit 
einer Laufzeit O(n). (Von anderen Postern sind es lediglich Varianten 
davon.)

Die In-Place Lösung von Scyte ist besser als deine; falls es denn ein 
Duplikat gibt. Die Komplexität ist aber mit O(n²) genauso hoch.

Eine In-Place Lösung, die die numerischen* Vorgaben ausnutzen könnte, 
sehe ich auf Anhieb nicht (*also bspw. unter Ausnutzung vom 
Restklassen).

Eine generische In-Place Lösung könnte iterativ die ersten m Elemente 
sortiert vorhalten, und das Element an (m+1) über Binärsuche prüfen / 
einfügen. Einfügen In-Place geht jedoch nicht in O(ld(m)). Daher wird 
man wohl mit sortierten Chunks arbeiten müssen und die "optimale" 
Komplexität O(n * ld(n)) nicht ganz erreichen.

Das oben vorgeschlagene Sortieren / Auffinden von Duplikaten in 
O(n*ld(n)) funktioniert hier nicht, da dabei die Position der Elemente 
verloren geht. Die Position wird aber als Rückgabewert benötigt.

von Michael B. (laberkopp)


Lesenswert?

Johannes schrieb:
> Der Code funktioniert auch

Tja, ist halt langsam
1
unordered_set<int> collect;
2
3
for(int x=0; x<a.size-1; x++)
4
{
5
    if(collect.find(a[x])!=collect.end()) return a[x];
6
    collect.insert(a[x]);
7
}
8
return -1;
Arc N. schrieb:
> In der Originalaufgabe steht meist noch Laufzeit O(n) und zusätzlicher
> Speicher O(1)

Öhm, nö, steht bei ihm nicht.

Und wenn er zu faul ist, die ganze Aufgabe abzuschrieben weil er meint 
das wäre egal - dann ist das sein Problem.

von Arc N. (arc)


Lesenswert?

Michael B. schrieb:
> Johannes schrieb:
>> Der Code funktioniert auch
>
> Tja, ist halt langsam
>
1
> unordered_set<int> collect;
2
> 
3
> for(int x=0; x<a.size-1; x++)
4
> {
5
>     if(collect.find(a[x])!=collect.end()) return a[x];
6
>     collect.insert(a[x]);
7
> }
8
> return -1;
9
>
> Arc N. schrieb:
>> In der Originalaufgabe steht meist noch Laufzeit O(n) und zusätzlicher
>> Speicher O(1)
>
> Öhm, nö, steht bei ihm nicht.
>
> Und wenn er zu faul ist, die ganze Aufgabe abzuschrieben weil er meint
> das wäre egal - dann ist das sein Problem.

Wenn das weggelassen wird, kann sich an Bucketsort/Countingsort 
angelehnt werden...
1
int firstDuplicate(arr_integer a) {
2
  int pos[MAX_ARRAY_SIZE + 1] = { 0 };
3
  int minIndex = INT_MAX;
4
  for (int i = 1; i < a.size + 1; i++) {
5
    int value = a.arr[i - 1];
6
    if (pos[value] == 0) {
7
      // never seen before
8
      pos[value] = -i;
9
    } else {
10
      if (pos[value] < 0) {
11
        // occured once before
12
        pos[value] = i;  
13
        // optimization:
14
        return i; // must be the lowest index so we can terminate here
15
      
16
      } else {
17
        // occured more than twice before
18
        // optimization: don't care, since this can't be the lowest index
19
        pos[value] = min(pos[value], i);
20
      }      
21
      minIndex = min(pos[value], minIndex);
22
    }
23
  }
24
  return (minIndex == INT_MAX) ? -1 : minIndex;
25
}
Ausgehend davon, kann jetzt das pos-Array weggelassen werden...

: Bearbeitet durch User
von Thomas G. (blasebalg)


Lesenswert?

Der TO spielt eh nicht mehr mit ;-)
1ms schlimmstenfalls auf nem Raspberry PI-A, kann das hinkommen ???
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <stdint.h>
4
5
#define NUMMAX   105
6
7
int get_the_first_evil_twins (const uint8_t *array, uint8_t size) {
8
  uint8_t help[NUMMAX + 1] = {0};   
9
  uint8_t cnt;
10
11
  for (cnt=0; cnt<size; cnt++) {
12
    help[(array[cnt])] +=1;
13
    if (help[(array[cnt])] == 2) return (int)array[cnt];
14
  }
15
  return -1;
16
}
17
18
int main (void) {
19
  uint8_t cnt, rnd;
20
  
21
  // Zufällige Array-Größe 1...NUMMAX
22
  printf ("random array values: 1...%i\n", NUMMAX);
23
  srand (time(NULL));
24
  rnd = rand() % NUMMAX + 1;
25
  uint8_t size = rnd;
26
  uint8_t my_array[size];
27
  // und Werte 1...NUMMAX
28
  for (cnt=0; cnt<size; cnt++) {
29
    rnd = rand() % NUMMAX + 1;
30
    printf("my_array[%i] = %i\n", cnt, rnd);
31
    my_array[cnt] = rnd;
32
  }
33
34
  // Los geht's
35
  int ret = get_the_first_evil_twins(&my_array[0], size);
36
  printf("Wert erste Doublette: %i\n", ret);
37
38
  return 0; 
39
}

von Mikro 7. (mikro77)


Lesenswert?

Arc N. schrieb:
> Wenn das weggelassen wird

...dann wurde bereits in der ersten Antwort die Lösung beschrieben und 
danach mehrfach wiederholt.

Wer hat eine In-Place Lösung die besser als O(n²) ist; ohne zusätzlichen 
Speicher und ohne Modifikation der Elemente ausserhalb ihres 
Wertebereichs: 1 ≤ a[i] ≤ a.length?

von Vlad T. (vlad_tepesch)


Lesenswert?

Mikro 7. schrieb:
> Wer hat eine In-Place Lösung die besser als O(n²) ist; ohne zusätzlichen
> Speicher und ohne Modifikation der Elemente ausserhalb ihres
> Wertebereichs: 1 ≤ a[i] ≤ a.length?

Die hab ich doch gepostet.
Achso - ohne Modifikation außerhalb ihres wb

Das heißt aber, dass du keine zusätzlichen Informationen anlegen kannst. 
Und bei 105 bietet ja so ein Byte noch ein freies Bit

: Bearbeitet durch User
von Egon N. (egon2321)


Lesenswert?

Johann L. schrieb:
> e statt extra Speicher.
>
> Passt doch.  a.length ist nach oben durch 150 beschränkt, d.h. ein
> zusätzliches Array mit a.length Elementen verbraucht zusätzlichen
> Speicher O(1) da durch eine Konstante unabhängig von der Eingabe nach
> oben abschätzbar.

Machs doch einfach wie bereits beschrieben, zuerst sortieren, dann 
durchiterieren.

std::sort wäre meine Wahl in C++, quicksort kann problematisch sein beim 
worst case. Wenn du aber nur ein paar Hundert Elemente hast, dann ist 
das egal.

https://www.geeksforgeeks.org/c-qsort-vs-c-sort/

Man darf übrigens nicht vernachlässigen, dass die Sprungvorhersage bei 
einem sortierten Array für eine relativ flotte Ausführung sorgt, da 
müsste man sich die Lösung von Vlad Tepesch mal im Vergleich bei 
entsprechend großen Datenmengen anschauen.

von Der Andere (Gast)


Lesenswert?

Thomas G. schrieb:
> Der TO spielt eh nicht mehr mit ;-)

Der hat ja auch was er wollte: jemanden gefunden der die (Haus)Aufgabe 
für ihn löst.
;-)

von Der Andere (Gast)


Lesenswert?

Egon N. schrieb:
> Machs doch einfach wie bereits beschrieben, zuerst sortieren, dann
> durchiterieren.

Und wie findest du dann den Wert des ersten Duplikats? Nach dem 
Sortieren stehen die Wert an anderen Stellen und bei dem Beispiel wäre 
dann die 2 die erste Doublette:

Johannes schrieb:
> Example
> For a = [2, 3, 3, 1, 5, 2], the output should be
> firstDuplicate(a) = 3.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Johannes schrieb:
> [execution time limit] 0.5 seconds (c)
>
>  ...
>
> Guaranteed constraints:
> 1 ≤ a.length ≤ 105,

Selbst der lausigste Algorithmus wird für die Bearbeitung der 105
Elemente nie 0,5 Sekunden benötigen. Selbst bei vollständigem paarweisen
Vergleich sind nur 102²=11025 Vergleiche erforderlich, was auch ein
relativ schwacher Prozessor locker in der Zeit schafft.

Woher kommt überhaupt die krumme Zahl 105? Wieso nimmt man nicht 100
oder 128? Wurde da vielleicht etwas falsch kopiert, und es sollte in
Wirklichkeit

  1 ≤ a.length ≤ 10⁵

heißen?

von Rolf M. (rmagnus)


Lesenswert?

Yalu X. schrieb:
> Wurde da vielleicht etwas falsch kopiert, und es sollte in
> Wirklichkeit
>
>   1 ≤ a.length ≤ 10⁵

Klingt plausibel.
Müsste man mal beim Original nachschauen, was wohl von codefights.com 
kommt. Allerdings braucht man einen Account, um da rein sehen zu können.
Der Link wäre 
https://codefights.com/interview-practice/task/pMvymcahZ8dY4g75q

von Thomas G. (blasebalg)


Lesenswert?

Vlad T. schrieb:
> Die hab ich doch gepostet.
> Achso - ohne Modifikation außerhalb ihres wb

So ganz geht das aber ned. Oder ich hab deinen Code oder die Aufgabe 
nicht richtig verstanden.
Array Größe 105 = a[0...104] Werte drinnen 1...105

int v = a[i] & 0x7fFFffFF;
bool seen = a[v] & 0x80000000;

Was passiert wenn a[i] = 105 ist... a[105] gibbet's ned.

Müßte doch dann ehr:
Edit:
int v = (a[i] -1) & 0x7fFFffFF;

Oder hab ich falsch gedacht?

: Bearbeitet durch User
von Michael B. (laberkopp)


Lesenswert?

Thomas G. schrieb:
> Oder hab ich falsch gedacht?

Die Werte drinnen könnten ja auch 0..104 sein.

von Rolf M. (rmagnus)


Lesenswert?

Michael B. schrieb:
> Die Werte drinnen könnten ja auch 0..104 sein.

Könnten sie, aber dürfen sie nicht:

Johannes schrieb:
> Guaranteed constraints:
> 1 ≤ a.length ≤ 105,
> 1 ≤ a[i] ≤ a.length.

von Vlad T. (vlad_tepesch)


Lesenswert?

das klingt plausibel.

Yalu X. schrieb:
> Und es sollte in
> Wirklichkeit
>
>   1 ≤ a.length ≤ 10⁵
>
> heißen?

Damit wäre auch meine Frage von oben geklärt:

Vlad T. schrieb:
> Wie wollen sie da messen, ob man da O(n) oder
> O(n³) code produziert?

Thomas G. schrieb:
> So ganz geht das aber ned. Oder ich hab deinen Code oder die Aufgabe
> nicht richtig verstanden.
> Array Größe 105 = a[0...104] Werte drinnen 1...105

der code sollte eher das Prinzip verdeutlichen.

Johannes schrieb:
> Guaranteed constraints:
> 1 ≤ a.length ≤ 105,
> 1 ≤ a[i] ≤ a.length.

da null nicht erlaubt ist, kann man alles um eins schieben, dann passt 
es.

ansonsten könnte man auch noch die obersten 2 bits benutzen, wenn man 
sicherstellt, dass sie nicht gebraucht werden.

von Vlad T. (vlad_tepesch)


Lesenswert?

Egon N. schrieb:
> Machs doch einfach wie bereits beschrieben, zuerst sortieren, dann
> durchiterieren.
>
> std::sort wäre meine Wahl in C++, quicksort kann problematisch sein beim
> worst case. Wenn du aber nur ein paar Hundert Elemente hast, dann ist
> das egal.

ne, sortieren geht nicht, dann sind ja die positionen verloren.

Und dann bist du bei der Rechenzeit mindestens bei O(n log n), anstelle 
bei O(n).
Wenn du die alten Indices behalten wills, musst du ne Tabelle mit den 
alten Indices beim sortieren mitpflegen, das heißt du brauchst auf jeden 
Fall mehr zusätzlichen Speicher, als die N/8 Bytes für den 
Lookup-Bitvektor

von Stefan A. (Gast)


Lesenswert?

habe mich grad mal angemeldet bei dieser Seite.
Es sind 10^5


und es ist die erste Aufgabe, die man Freischalten muss..

von Vlad T. (vlad_tepesch)


Lesenswert?

Stefan A. schrieb:
> habe mich grad mal angemeldet bei dieser Seite. Es sind 10^5
>
> und es ist die erste Aufgabe, die man Freischalten muss..

Okay, also zu groß für 16bit und bei 32 sind genug bis frei, dass man da 
noch ne Menge reinpacken kann ;-)

von Egon N. (egon2321)


Lesenswert?

Vlad T. schrieb:
> ne, sortieren geht nicht, dann sind ja die positionen verloren.
>
> Und dann bist du bei der Rechenzeit mindestens bei O(n log n), anstelle
> bei O(n).
> Wenn du die alten Indices behalten wills, musst du ne Tabelle mit den
> alten Indices beim sortieren mitpflegen, das heißt du brauchst auf jeden
> Fall mehr zusätzlichen Speicher, als die N/8 Bytes für den
> Lookup-Bitvektor

O(n) und ohne weiteren Speicher geht nunmal nicht. Einen Tod wirst du 
sterben müssen.

von Stefan (Gast)


Lesenswert?

Egon N. schrieb:
> geht nunmal nicht

Nun ja, die Aufgabe ist ja sehr speziell formuliert -- wenn alle Werte > 
0 sind, kann man ja einfach die schon gefundenen Werte durch einen 
negativen Wert markieren, etwa für 7 Zahlen nach
1
import random
2
3
proc main(): int =
4
  const N = 7
5
  var a = newSeq[int](N + 1) # N + 1 elements, we ignore position 0
6
  for i in 0 .. a.high:
7
    a[i] = i
8
  a.shuffle
9
  for i in 0 .. a.high: # put value 0 back at pos 0
10
    if a[i] == 0:
11
      swap(a[i], a[0])
12
      break 
13
  a[5] = a[2] # create arbitrary duplicate
14
  echo a[1 .. ^1]
15
  for i in 1 .. a.high:
16
    let v = a[i].abs
17
    if a[v] < 0:
18
      echo "Duplicate found at position ", i, '(', v, ')'
19
      return i
20
    else:
21
      a[v] *= -1 # mark as already seen
22
  echo "No duplicates found"
23
  return -1
24
25
echo "return value is: ", main()

Ergibt dann

[code]
$ ./f
@[3, 2, 6, 1, 2, 4, 7]
Duplicate found at position 5(2)
return value is: 5

[\code]

von Stefan (Gast)


Lesenswert?


von Vlad T. (vlad_tepesch)


Lesenswert?

Egon N. schrieb:
> O(n) und ohne weiteren Speicher geht nunmal nicht. Einen Tod wirst du
> sterben müssen.

der zusätzliche speicher ist ja schon da, da die Inputdaten weniger Bits 
Informaionen beinhalten, als der der nächst größere Datentyp 
bereistellt.

Stefan schrieb:
> Ach, hatte Vlad ja eh schon geschrieben...

Deine Lösung, das entsprechende Feld zu negieren, find ich eleganter 
(vorausgesetzt natürlich, der datentyp ist signed). ist nich soviel 
bitgefummel und leicher lesbar

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.